https://www.algospot.com/judge/problem/read/BOARDCOVER
algospot.com :: BOARDCOVER
게임판 덮기 문제 정보 문제 H*W 크기의 게임판이 있습니다. 게임판은 검은 칸과 흰 칸으로 구성된 격자 모양을 하고 있는데 이 중 모든 흰 칸을 3칸짜리 L자 모양의 블록으로 덮고 싶습니다. 이
www.algospot.com
코드설명
브루트포스를 활용합니다.
문제에서 유의해야할 점들입니다.
1. coverType, 즉 격자 모양을 생성할때, 빈칸 중에서 가장위, 그 중에서도 가장 왼쪽에 있는 칸을 처음 채운다고 가정해야 합니다. 따라서 그 왼쪽과 위에 있는 칸은 항상 이미 채워져있다고 가정할 수 있습니다.
처음에 해당 사항을 놓치고, 아래와 같이 하였기에 격자칸을 모두 카운팅하지 못했습니다.
잘못된 블록입니다. 두번째를 보면 {0, 1} 로 처리합니다.
public static int[][][] coverType = { { {0, 0}, {0, 1}, {1, 1} }, { {0, 1}, {1, 0}, {1, 1} }, { {0, 0}, {1, 0}, {1, 1} }, { {0, 0}, {0, 1}, {1, 0} } };
올바르게 바꾼 방식입니다. 가장 왼쪽 칸은 항상 채우도록 하고, 가장 왼쪽 칸 이전은 반드시 채우는 방안으로 해야 합니다. 이유는 다시는 이전 칸으로 돌아가지 않기 때문입니다. 즉, 앞으로는 이전 칸은 전혀 고려하지 않고 앞으로의 칸만 고려할 것 입니다.
public static int[][][] block = new int[][][] //[4][][] { // [4][3][] { //[4][3][2] {0, 0}, {1, 0}, {1, 1} }, { {0, 0}, {0, 1}, {1, 0} }, { {0, 0}, {0, 1}, {1, 1} }, { {0, 0}, {1, -1}, {1, 0} } };
만들다보면, 이전의 격자 칸의 모양이 만약 맞지 않으면 어떻게하지? 라는 고민이 생길 수 있는데 이전 칸은 반드시 채워졌다고 가정하면서 진행하므로 앞으로의 채우는 과정만 신경쓰면 됩니다.
2. 매번 아직 채우지 못한 칸 중 가장 윗줄 왼쪽에 있는 칸을 찾습니다. 코드로는 기저사례 부분입니다.
//아직 채우지 못한 칸 중 가장 윗줄 왼쪽에 있는 칸을 찾는다. int r = -1, c = -1; for(int i=0;i<board.length;i++) { for(int j=0;j<board[i].length;j++) { if(board[i][j] == 0) { r = i; c = j; break; } } if(c != -1) break; } //기저 사례 ; 모든 칸을 채웠으면 1을 반환한다. if(c == -1) return 1;
답의 상한을 계산해봅니다.
흰칸이 최대 개수는 50개입니다. 그러므로 [50/3] = 16개의 블록을 놓을 수 있기에 가능한 답의 상환은 4^16 (블록 16개를 총 4개의 경우의 수로 설정할 수 있음) = 2^32 ( 약 40억 .
이를 보면, 도저히 시간 내에 생성할 수 없을 것 같지만, 실제 입력을 손으로 풀어보면 각 단계에서 우리가 선택할 수 있는 블록 배치가 크게 제한됨을 알 수 있습니다. 예를 들어 흰 칸이 6칸 있는 입력이 주어진다면 이 이론상으로는 4^2 = 16가지의 방법이 있어야 하는데 , 실제로는 최대 두가지 방법밖에 없습니다. 흰칸이 48개 있는 예제 입력 3을 보더라도 답이 1,514가 나오기에 시간 안에 답을 구할 수 있음을 알 수 있습니다.
코드
package algorhythm; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.StringTokenizer; public class Main { public static int C, N, M; public static int answer = 0; public static char[][] map; //[4개의 회전방향][3개의 칸][각 요소 2개] //첫 시작은 ㄱ 모양으로 해서 시계방향으로 회전시킬 것 이다. //[4][3][2] 형태로 만들 것 입니다. public static int[][][] coverType = { { {0, 0}, {0, 1}, {1, 0} }, { {0, 0}, {0, 1}, {1, 1} }, { {0, 0}, {1, 0}, {1, 1} }, { {0, 0}, {1, 0}, {1, -1} } }; public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); C = Integer.parseInt(st.nextToken()); while(C-- > 0) { st = new StringTokenizer(br.readLine()); int H = Integer.parseInt(st.nextToken()); int W = Integer.parseInt(st.nextToken()); map = new char[H][W]; for(int i=0;i<H;i++) { st = new StringTokenizer(br.readLine()); map[i] = st.nextToken().toCharArray(); } int[][] board = new int[H][W]; for(int i=0;i<H;i++) { for(int j=0;j<W;j++) { board[i][j] = (map[i][j] == '#' ? 1 : 0); //만약 # 이면 검은 칸입니다. } } answer = cover(board); System.out.println(answer); } } //board의 (y, x)를 type번 방법으로 덮거나, 덮었던 블록을 없앤다. //delta = 1 이면 덮고, -1 이면 덮었던 블록을 없앤다. //만약 블록이 제대로 덮이지 않은 경우( 게임판 밖으로 나가거나, 겹치거나, 검은 칸을 덮을 때) false를 반환한다. public static boolean set(int[][] board, int r, int c, int type, int delta) { boolean ok = true; for(int i=0;i<3;i++) { int nr = r + coverType[type][i][0]; int nc = c + coverType[type][i][1]; if( nr < 0 || nr >= board.length || nc < 0 || nc >= board[0].length) { ok = false; } else if( (board[nr][nc] += delta) > 1) { ok = false; } } return ok; } // board의 모든 빈칸을 덮을 수 있는 방법의 수를 반환한다. // board[i][j] = 1 이미 덮인 칸 혹은 검은 칸 // board[i][j] = 0 아직 덮이지 않은 칸 public static int cover(int[][] board) { //아직 채우지 못한 칸 중 가장 윗줄 왼쪽에 있는 칸을 찾는다. int r = -1, c = -1; for(int i=0;i<board.length;i++) { for(int j=0;j<board[i].length;j++) { if(board[i][j] == 0) { r = i; c = j; break; } } if(c != -1) break; } //기저 사례 ; 모든 칸을 채웠으면 1을 반환한다. if(c == -1) return 1; int ret = 0; for(int type = 0; type < 4; type++) { //만약 board[y][x]를 type형태로 덮을 수 있으면 재귀 호출한다. if( set(board, r, c, type, 1) ) ret += cover(board); //덮었던 블록을 치운다. set(board, r, c, type, -1); } return ret; } }
다르게 풀어본 코드입니다. 속도는 첫번쨰 코드보다 느립니다.
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.StringTokenizer; public class Main { public static int C, N, M, H, W; public static int answer = Integer.MAX_VALUE; public static int[][] map; public static char[][] mapInput; public static int whiteBoxCnt; public static int[][][] block = new int[][][] //[4][][] { // [4][3][] { //[4][3][2] {0, 0}, {1, 0}, {1, 1} }, { {0, 0}, {0, 1}, {1, 0} }, { {0, 0}, {0, 1}, {1, 1} }, { {0, 0}, {1, -1}, {1, 0} } }; public static void main(String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); C = Integer.parseInt(st.nextToken()); while(C-- > 0) { st = new StringTokenizer(br.readLine()); H = Integer.parseInt(st.nextToken()); W = Integer.parseInt(st.nextToken()); mapInput = new char[H][W]; map = new int[H][W]; for(int i=0;i<H;i++) { st = new StringTokenizer(br.readLine()); mapInput[i] = st.nextToken().toCharArray(); } whiteBoxCnt = 0; for(int i=0;i<H; i++) { for(int j=0;j<W; j++) { if(mapInput[i][j] == '#') { map[i][j] = 1; }else { whiteBoxCnt += 1; map[i][j] = 0; } } } System.out.println(getBoardCoverCnt(0,0, 0)); } } public static int getBoardCoverCnt(int r, int c, int whiteCnt) { //기저사례 1: if(c >= W) { r = r + 1; c = 0; } // if(r == H-1 && c == W - 1 && whiteBoxCnt == whiteCnt) { // return 1; // } if(r == H-1 && c == W - 1 && isAllCovered() == true) { return 1; } if(r < 0 || r >= H || c < 0 || c >= W) return 0; int ret = 0; if(map[r][c] == 1) { //만약 이미 채워진 칸이라면 칸을 채우지 않고, 다음칸으로 넘어갑니다. ret += getBoardCoverCnt(r, c + 1, whiteCnt); return ret; } for(int type = 0; type < 4; type++) { if(map[r][c] == 0) { boolean isSuccess = cover(r, c, type, +1); if(isSuccess) { ret += getBoardCoverCnt(r, c + 1, whiteCnt + 3); } cover(r, c, type, -1); } } return ret; } public static boolean cover(int r, int c, int type, int value) { boolean isSuccess = true; for(int order = 0; order < 3; order++) { int nr = r + block[type][order][0]; int nc = c + block[type][order][1]; if(nr < 0 || nr >= H || nc < 0 || nc >= W) { isSuccess = false; continue; } map[nr][nc] += value; } return isSuccess; } public static boolean isAllCovered() { for(int i=0;i<H;i++) { for(int j=0;j<W;j++) { if(map[i][j] != 1) { return false; } } } return true; } }