https://www.acmicpc.net/problem/17136
코드설명
브루트포스(Brute-Force) + 백트래킹(BackTracking) 문제입니다.
문제에서 유의해야할점은,
- 색종이를 붙일때는 길이가 11부터 종이를 넘어갔다고 판단해야합니다.
public static boolean checkArr(int x, int y, int length) {
boolean successFlag = true;
if( x + length > 10 || y + length > 10 ) {
return false;
}
for(int i=x; i < x + length;i++) {
for(int j=y; j < y + length; j++) {
if( arr[i][j] == 0) {
successFlag = false;
return false;
}
}
}
return successFlag;
}
- 예시로 들어보면, (0,5)에서 5번째 종이를 붙이면 (0, 10)이므로 10으로써 종이를 나간다고 판단한다면 5x5 종이를 붙일 수 없습니다.
2. 만약 arr[nowX][nowY] 가 1이 아니어서 색종이를 붙이지 않는경우에는 다음칸으로 넘어가기 위해 else처리하여 다음칸으로 넘어가야합니다
if(arr[nowX][nowY] == 1) {
for(int k=5;k>=1;k--) {
//5x5, 4x4 내림차순으로 검사해야합니다.
if( checkArr(nowX, nowY, k) == true) {
if(usedPaper[k] > 0) {
usedPaper[k] -= 1;
checkPaperArr(nowX, nowY, k);
simulate(nowX, nowY + k);
erasePaperArr(nowX, nowY, k);
usedPaper[k] += 1;
}
}
}
}
else {
simulate(nowX, nowY + 1);
}
- arr[nowX][nowY] != 1 을 의미하는 else 문 로직을 추가하지 않는다면, 1이 없는경우 진전하지 않아 연산이 진행되지 않습니다.
3. 5x5, 4x4, 3x3 의 내림차순으로 색종이를 붙이지 않고도 1x1 -> 2x2 이런형식으로 붙여도 됩니다.
- 색종이의 길이가 긴것부터 처리해야만 최소의 색종이로 붙일 수 있을거라고 생각하였는데 이번 문제 같은경우 완전탐색문제로써 어떤 색종이를 먼저 처리하든 결국은 모든 색종이를 처리하므로 상관이 없습니다.
이번 문제같은경우 2580 스도쿠 문제와 유사하다고 느꼈습니다.
코드
가장 빠른 속도의 코드입니다.
이 코드같은경우, 먼저 map에서 주어진 1의 개수를 구하고, 1을 색칠할떄마다 개수를 재귀함수에 추가하여, 모든 숫자를 다 색칠했다면 바로 종료되게 만들었습니다.
또한, 이전에는 Map 전체를 재귀함수 종료시 다시 원상복구에 사용했는데 간단하게 생각해보면, 어처피 색종이를 붙이려면 색종이가 붙는 칸이 모두 1이어야 하므로, 그 색종이의 SIZE만큼만 모두 1로 원상복구 시키면 됩니다.
또한 색종이를 붙인 이후에 해당 칸의 Col 값에 색종이의 SIZE만큼 바로 이동시켜주어 불필요 연산을 줄여주었습니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
public static int N, M, D;
public static int answer = Integer.MAX_VALUE, oneCnt = 0;
public static int[][] map = new int[10][10];
public static boolean[][] coloredMap = new boolean[10][10];
public static int[] colorPaper = new int[] { 5, 5, 5, 5, 5 };
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// StringTokenizer st = new StringTokenizer(br.readLine());
for(int i=0;i<10;i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<10;j++) {
map[i][j] = Integer.parseInt(st.nextToken());
if(map[i][j] == 1) {
oneCnt += 1;
}
}
}
simulate(0, 0, 0, 0, 0);
if(answer == Integer.MAX_VALUE) {
answer = -1;
}
System.out.println(answer);
}
public static void simulate(int level, int r, int c, int colored, int used) {
if(oneCnt == colored) {
answer = Math.min(answer, used);
return ;
}
if(c >= 10) {
r = r + 1;
c = 0;
}
if(r >= 10) {
return ;
}
if(map[r][c] == 1) {
//해당 map[r][c]에서 SIZE를 구합니다.
for(int kind=0; kind < 5; kind++) {
//kind만큼의 길이로 색종이를 구할 것 입니다.
boolean colorableFlag = true;
int coloredCnt = 0;
//만약 해당 색종이를 모두 다 썼다면 애초에 불가능합니다.
if(colorPaper[kind] <= 0) continue;
//만약 SIZE가 넘칠경우 애초에 불가능합니다.
//r도 포함이므로 범위는 r + kind 입니다.
if(r + kind < 0 || r + kind >= 10 || c + kind < 0 || c + kind >= 10) continue;
//해당 칸에서 1x1, 2x2, 3x3, 4x4, 5x5 로 색칠할 수 있는지 확인할 것 입니다.
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
if(map[r+i][c+j] == 0) {
colorableFlag = false;
}
}
}
//만약 해당 Size의 종이로 색칠할 수 있다면,
if(colorableFlag == true) {
colorPaper[kind] -= 1;
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
coloredCnt += 1;
map[r+i][c+j] = 0;
}
}
simulate(level + 1, r, c + kind + 1, colored + coloredCnt, used + 1);
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
map[r+i][c+j] = 1;
}
}
colorPaper[kind] += 1;
}
}
}
else if(map[r][c] == 0){
simulate(level + 1, r, c + 1, colored, used);
}
}
}
함수적으로 풀어본 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Main {
public static int N;
public static int[][] arr;
public static int[][] visited;
public static int[] usedPaper;
public static int answer = Integer.MAX_VALUE;
public static StringBuilder sb = new StringBuilder();
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// StringTokenizer st = new StringTokenizer(br.readLine());
arr = new int[10][10];
visited = new int[10][10];
usedPaper = new int[6];
for(int i=0;i<10;i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<10;j++) {
arr[i][j] = Integer.parseInt(st.nextToken());
}
}
for(int i=1;i<6;i++) {
usedPaper[i] = 5;
}
simulate(0, 0);
if(answer == Integer.MAX_VALUE) {
System.out.println("-1");
}else {
System.out.println(answer);
}
}
public static void simulate(int nowX, int nowY) {
boolean endFlag = true;
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
if(arr[i][j] == 1) {
endFlag = false;
break;
}
}
}
if(endFlag == true) {
int answerCnt = 0;
for(int i=1;i<6;i++) {
answerCnt += 5 - usedPaper[i];
}
answer = Math.min(answer, answerCnt);
return ;
}
if(nowX >= 9 && nowY >= 10)
return ;
if(nowY >= 10) {
nowX += 1;
nowY = 0;
}
if(arr[nowX][nowY] == 1) {
for(int k=5;k>=1;k--) {
//5x5, 4x4 내림차순으로 검사해야합니다. ( 완전탐색이기에 오름차순으로 해도 상관없습니다. )
if( checkArr(nowX, nowY, k) == true) {
if(usedPaper[k] > 0) {
usedPaper[k] -= 1;
checkPaperArr(nowX, nowY, k);
simulate(nowX, nowY + k);
erasePaperArr(nowX, nowY, k);
usedPaper[k] += 1;
}
}
}
}
else {
simulate(nowX, nowY + 1);
}
}
public static boolean checkArr(int x, int y, int length) {
boolean successFlag = true;
if( x + length > 10 || y + length > 10 ) {
return false;
}
for(int i=x; i < x + length;i++) {
for(int j=y; j < y + length; j++) {
if( arr[i][j] == 0) {
successFlag = false;
return false;
}
}
}
return successFlag;
}
public static void checkPaperArr(int x, int y, int length) {
for(int i=x; i < x + length;i++) {
for(int j=y; j < y + length; j++) {
arr[i][j] = 0;
}
}
}
public static void erasePaperArr(int x, int y, int length) {
for(int i=x; i < x + length;i++) {
for(int j=y; j < y + length; j++) {
arr[i][j] = 1;
}
}
}
}
불필요하게 시간이 오래걸리는 코드입니다.
시간이 오래걸리는 이유는, 완전탐색(Brute-Force) 과정에서 전체 Map을 모두 돌면서 이전 Level의 Map으로 되돌립니다, 어처피 색칠을 할경우 1인 부분만 색칠을 하므로 색종이를 붙였다면, 그 색종이 안의 공간은 모두 1 로 다시 되돌리면 됩니다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
public static int N, M, D;
public static int answer = Integer.MAX_VALUE, oneCnt = 0;
public static int[][] map = new int[10][10];
public static boolean[][] coloredMap = new boolean[10][10];
public static int[] colorPaper = new int[] { 5, 5, 5, 5, 5 };
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// StringTokenizer st = new StringTokenizer(br.readLine());
for(int i=0;i<10;i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<10;j++) {
map[i][j] = Integer.parseInt(st.nextToken());
if(map[i][j] == 1) {
oneCnt += 1;
}
}
}
simulate(0, 0, 0, 0, 0);
if(answer == Integer.MAX_VALUE) {
answer = -1;
}
System.out.println(answer);
}
public static void simulate(int level, int r, int c, int colored, int used) {
if(oneCnt == colored) {
answer = Math.min(answer, used);
return ;
}
if(c >= 10) {
r = r + 1;
c = 0;
}
if(r >= 10) {
return ;
}
int[][] storeMap = new int[10][10];
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
storeMap[i][j] = map[i][j];
}
}
if(map[r][c] == 1) {
//해당 map[r][c]에서 SIZE를 구합니다.
for(int kind=0; kind < 5; kind++) {
//kind만큼의 길이로 색종이를 구할 것 입니다.
boolean colorableFlag = true;
int coloredCnt = 0;
//만약 해당 색종이를 모두 다 썼다면 애초에 불가능합니다.
if(colorPaper[kind] <= 0) continue;
//만약 SIZE가 넘칠경우 애초에 불가능합니다.
//r도 포함이므로 범위는 r + kind 입니다.
if(r + kind < 0 || r + kind >= 10 || c + kind < 0 || c + kind >= 10) continue;
//해당 칸에서 1x1, 2x2, 3x3, 4x4, 5x5 로 색칠할 수 있는지 확인할 것 입니다.
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
if(map[r+i][c+j] == 0) {
colorableFlag = false;
}
}
}
//만약 해당 Size의 종이로 색칠할 수 있다면,
if(colorableFlag == true) {
colorPaper[kind] -= 1;
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
if(map[r+i][c+j] == 1) {
coloredCnt += 1;
map[r+i][c+j] = 0;
}
}
}
simulate(level + 1, r, c + kind + 1, colored + coloredCnt, used + 1);
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
map[i][j] = storeMap[i][j];
}
}
colorPaper[kind] += 1;
}
}
}
else if(map[r][c] == 0){
simulate(level + 1, r, c + 1, colored, used);
}
}
}
처음에 문제에서 0이 적힌 칸에 색종이가 있으면 안된다라는 조건을 읽지않고, 풀었습니다.
가장 큰 최소한의 종이를 구하기 위해 덮을 수 있는 가장큰 종이로 덮어서 진행했습니다.
이렇게 풀경우 따로 색종이를 붙였다는 Boolean[][] 배열을 사용해서 색종이가 붙였는지 안붙여있는지 확인할 수 있습니다.
이 과정에서 실수했던점은, kind는 0 으로 시작하여 해당 값도 포함입니다.
하지만, 처음에는 r + kind +1 로 처리하여 색종이의 길이가 1개 더 길어지는 오류가 있었습니다.
아래와 같이 수정했습니다.
if(r + kind < 0 || r + kind >= 10 || c + kind < 0 || c + kind >= 10) continue;
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1
답 : 4
0 0 0 0 0 0 0 0 0 0
0 1 1 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 1 1 0 0 0 0
0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
답 : 2
적절한 답은 5 이지만 색종이가 0 에도 놓일 수 있는 상태에서 최소값은 2입니다.
package algorhythm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
public class Main {
public static int N, M, D;
public static int answer = Integer.MAX_VALUE, oneCnt = 0;
public static int[][] map = new int[10][10];
public static boolean[][] coloredMap = new boolean[10][10];
public static int[] colorPaper = new int[] { 5, 5, 5, 5, 5 };
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// StringTokenizer st = new StringTokenizer(br.readLine());
for(int i=0;i<10;i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
for(int j=0;j<10;j++) {
map[i][j] = Integer.parseInt(st.nextToken());
if(map[i][j] == 1) {
oneCnt += 1;
}
}
}
System.out.println(oneCnt);
simulate(0, 0, 0, 0, 0);
System.out.println(answer);
}
public static void simulate(int level, int r, int c, int colored, int used) {
// System.out.println("Colored:"+colored);
if(oneCnt == colored) {
answer = Math.min(answer, used);
return ;
}
if(c >= 10) {
r = r + 1;
c = 0;
}
if(r >= 10) {
return ;
}
boolean[][] storeColoredMap = new boolean[10][10];
int[][] storeMap = new int[10][10];
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
storeColoredMap[i][j] = coloredMap[i][j];
storeMap[i][j] = map[i][j];
}
}
if(map[r][c] == 1) {
for(int kind = 0; kind < 5; kind ++) {
boolean coloredSuccessFlag = true;
if(r + kind < 0 || r + kind >= 10 || c + kind < 0 || c + kind >= 10) continue;
if(colorPaper[kind] > 0) {
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
//만약 이미 색칠된 것이 하나라도 존재한다면, 실패입니다.
if(coloredMap[r+i][c+j] == true) {
coloredSuccessFlag = false;
}
}
}
if(coloredSuccessFlag == true) {
int coloredCnt = 0;
colorPaper[kind] -= 1;
for(int i=0; i<kind + 1; i++) {
for(int j=0; j<kind + 1; j++) {
coloredMap[r+i][c+j] = true;
if(map[r+i][c+j] == 1) {
map[r+i][c+j] = 0;
coloredCnt += 1;
}
}
}
simulate(level + 1, r, c + kind, colored + coloredCnt, used + 1);
for(int i=0;i<10;i++) {
for(int j=0;j<10;j++) {
coloredMap[i][j] = storeColoredMap[i][j];
map[i][j] = storeMap[i][j];
}
}
colorPaper[kind] += 1;
}
}
}
}
else if(map[r][c] == 0){
simulate(level + 1, r, c + 1, colored, used);
}
}
}
'알고리즘 > 백준' 카테고리의 다른 글
[백준] 2669 직사각형 네개의 합집합의 면적 구하기 - 구현 JAVA (0) | 2023.08.24 |
---|---|
[백준] 1759 암호 만들기 - 백트래킹 + Stream JAVA (0) | 2023.08.23 |
[백준] 2661 좋은수열 - 백트래킹(Backtracking) + 문자열(String) + 아이디어(Idea) JAVA (0) | 2023.08.22 |
[백준] 1062 가르침 - 백트래킹(BackTracking) + 조합(Combination) JAVA (0) | 2023.08.22 |
[백준] 2580 스도쿠 - 백트래킹(Backtracking) + DFS(깊이우선탐색) JAVA (0) | 2023.08.21 |