1. 문제 링크
https://www.acmicpc.net/problem/17136
17136번: 색종이 붙이기
<그림 1>과 같이 정사각형 모양을 한 다섯 종류의 색종이가 있다. 색종이의 크기는 1×1, 2×2, 3×3, 4×4, 5×5로 총 다섯 종류가 있으며, 각 종류의 색종이는 5개씩 가지고 있다. <그림 1> 색종이를 크
www.acmicpc.net
2. 접근법
1. 변수 선언
cntOne: 1이 적힌 칸의 개수 (1이 적힌 모든 칸을 방문했는지의 여부를 판단하기 위해)
numOfPapers[]: 색종이 사이즈당 남은 색종이의 수
board[][]: 입력
2. backTracking(int depth)
색종이를 붙일 때마다 depth 증가
즉, depth가 붙인 색종이의 수이므로 최솟값을 담은 ans와 비교하여 depth가 더 크다면 종료
또한 cntOne을 사용하여 1이 적힌 칸이 남아있지 않다면, ans와 비교하여 종료
이중 for문으로 board를 탐색하면서 1이 적힌 칸을 만났다면,
해당 칸(i, j)를 기준으로 calcualteMaxPaperSize() 함수를 통해 덮을 수 있는 색종이의 최대 사이즈 구하기
maxSize부터 1까지, 각 사이즈에 대하여 해당 칸(i, j)를 덮는 경우로 나누어 재귀적으로 backTracking()을 호출하여 탐색 이어가기
각 사이즈의 색종이로 덮을 때, 해당 색종이가 더이상 남지 않았다면 (numOfPapers[size] == 0) 건너뛰기
그렇지 않다면, 해당 사이즈의 색종이 개수 줄이기
(i, j)를 기준으로 size만큼 board[][]를 0으로 바꾸기
이때 바꾼 칸의 개수를 cntOfDeleted 변수를 사용하여 저장하기
cntOne에 cntOfDeleted만큼 빼서 남은 1이 적힌 칸의 개수 구하기
backTracking(depth +1)을 호출해서 이후 덮을 색종이를 탐색해나감
backTracking() 이후에는 cntOne과 board[][], numOfPapers[]를 복구함
3. calculateMaxPaperSize(int r, int c)
(r, c)칸이 가장 왼쪽 위에 위치한 경우, 덮을 수 있는 색종이의 최대 사이즈 구하기
주변을 탐색해서 모두 1로 구성되어 있는지 판별함
3. 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
import java.io.IOException;
// 색종이 붙이기
public class BJ17136 {
static int cntOne; // 1이 적힌 칸의 개수
static int ans = Integer.MAX_VALUE;
static int[] numOfPapers = {0, 5, 5, 5, 5, 5};
static boolean[][] board = new boolean[10][10];
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
for(int i = 0; i < 10; i++) {
st = new StringTokenizer(br.readLine());
for(int j = 0; j < 10; j++) {
if(st.nextToken().equals("1")) {
board[i][j] = true;
cntOne++;
}
}
}
backTracking(0);
if(ans == Integer.MAX_VALUE) ans = -1;
System.out.println(ans);
}
private static void backTracking(int depth) { // depth는 붙인 색종이 개수
if(depth >= ans) return; // 최솟값보다 크다면 종료
if(cntOne == 0) { // 1이 적힌 칸이 남지 않았다면, 즉 색종이로 모두 덮었다면
ans = Integer.min(ans, depth);
return;
}
for(int i = 0; i < 10; i++) {
for(int j = 0; j < 10; j++) {
if(board[i][j]) { // 1이 적힌 칸을 만났을 때
int maxSize = calculateMaxPaperSize(i, j); // 해당 칸을 기준으로 덮을 수 있는 색종이의 최대 사이즈
for(int size = maxSize; size >= 1; size--) { // 각 사이즈에 대하여 덮는 경우를 나누어 탐색
// size에 해당하는 색종이가 더이상 남지 않았다면 패스
if(numOfPapers[size] == 0) continue;
// size에 해당하는 색종이 개수 줄이기
numOfPapers[size]--;
int cntOfDeleted = 0;
// (i, j)를 기준으로 size만큼 board[][]를 0으로 바꾸기
for(int r = i; r < i + size; r++) {
for(int c = j; c < j + size; c++) {
board[r][c] = false;
cntOfDeleted++;
}
}
// 1이 적힌 칸은 줄어듦
cntOne -= cntOfDeleted;
backTracking(depth + 1);
// 1이 적힌 칸 복구
cntOne += cntOfDeleted;
// board[][]를 1로 복구
for(int r = i; r < i + size; r++) {
for(int c = j; c < j + size; c++) {
board[r][c] = true;
}
}
// size에 해당하는 색종이 개수 복구
numOfPapers[size]++;
}
return; // 1이 적힌 칸을 만났으면 색종이로 덮어야 했는데, 그러지 못하고 도달했으므로 유효하지 않은 탐색이기에 종료
}
}
}
}
private static int calculateMaxPaperSize(int r, int c) {
// (r, c)를 기준으로 덮을 수 있는 색종이의 최대 사이즈 계산하기
for(int cnt = 1; cnt <= 4; cnt++) {
for(int i = r; i <= r + cnt; i++) {
for(int j = c; j <= c + cnt; j++) {
if(i >= r + cnt || j <= c + cnt) {
if(i >= 10 || j >= 10 || !board[i][j]) return cnt;
}
}
}
}
return 5;
}
}
4. 막힌 부분
backTracking 종료 조건을 캐치하는 데 어려움이 있었다.
1.
처음에 backTracking()에 파라미터를 넘겨주지 않고 구현했었다.
답은 numOfPapers[] 에 남은 색종이 수가 담겨있기에, 해당 배열을 사용해서 사용한 색종이 수를 답으로 구했었다.
그런데 backTracking의 depth가 결국 붙인 색종이 수이므로, depth를 사용해서 답을 구하는 것이 더 깔끔한 풀이였고
최솟값을 답으로 구해야 했기에 backTracking 함수 내에서 if문을 걸어 종료 조건을 추가하는 것이 더욱 효율적으로 코드를 짜는 방법이었다.
2.
1이 적힌 칸은 만났다면 무조건 해당 칸을 색종이로 덮을 수 있어야 하는데, 즉 backTracking()을 재호출해야 한다.
그런데 그러지 못한 경우, 색종이로 덮지 못했다는 의미이므로 탐색을 종료해야 한다. 이걸 못 찾았다!! 그래서 무한 루프에 빠지는 케이스가 있었다.
---
재귀는 어렵다..
일단 감으로 구현은 할 수 있는데 구현하고도 내 코드가 어떻게 동작하는지 따라가야 이해할 수 있었던 풀이였다. (엉망진창..)
백트래킹 유형의 경우 종료 조건을 찾아낸 후 구현하도록 하자.
'Algorithm' 카테고리의 다른 글
[BOJ] 1194 달이 차오른다, 가자. (Java) (0) | 2023.09.27 |
---|---|
[BOJ] 16637 괄호 추가하기 (Java) (0) | 2023.07.22 |
[BOJ] 17070 파이프 옮기기 1 (Java) (0) | 2023.07.16 |
[BOJ] 21609 상어 중학교 (Java) (0) | 2023.07.08 |
[BOJ] 1062 가르침 (Java) (0) | 2023.06.26 |