[1. 문제 재정의 및 추상화]
- H*W 크기의 게임판에서 모든 흰 칸을 세칸짜리 L 자 모양의 블록으로 덮어야 함.
- 이 형태의 블록은 회전해서 놓을 수 있지만, 서로 겹치거나, 검은색 칸을 덮거나, 게임판 밖으로 나가서는 안됨.
- '#' 은 검은 칸, '.' 은 흰 칸을 의미함
- H,W 는 최소 1 에서 최대 20 이며, 흰 칸의 수는 50을 넘지 않는다.
- 흰 칸을 덮는 경우의 개수를 계산해야 함.
[2. 해결 계획]
- 가능한 경우의 수 이므로 완전 탐색 방식을 사용해 본다.
- 흰 칸의 개수가 3의 배수가 아니면 어떤 방식으로도 흰 칸을 모두 덮을 수 없다.
=> L 자 모양은 세칸 짜리 이다. - 블록을 놓는 순서는 중요하지 않다.
=> (A):└, (B): ┘가 있을 때 (A) 를 먼저 놓고, (B) 를 놓나 / (B) 를 놓고 (A) 를 놓는 것은 같은 경우이다. - 블록을 놓는 순서를 강제하도록 한다.
=> 좌상단에 먼저 블록을 회전 시켜가면서 배치하고, 그 다음은 재귀로 반복한다.
=> Base Case: 더 이상 블록을 놓을 수 없는 경우 - 좌상단에 블록을 먼저 배치 할 때 놓을 수 있는 방식은 아래 4가지와 같음
- 각 경우에서 * 를 기준으로 X, Y 에 대한 상대좌표를 구할 수 있음
[3. 계획 검증]
- 한 블록마다 최대 4가지의 선택이 있으며, 입력에서 흰 칸의 최대 개수는 50이므로, 최대 floor(50/3) = 16 개의 블록마다 4 개의 경우가 발생하므로, 4^16 = 2^32 이지만 블록을 놓은 방식이 정해지면, 그 다음 블록이 정해지는 경우는 한정되게 된다.
=> 아래와 같은 경우에서 * 를 기준으로 흰 블록의 모양이 결정되면, 다음 위치에 놓일 블록은 한개로 고정 될 수 밖에 없다.
=> 이런식으로 답의 상한은 2^32 보다 더 작다고 볼 수 있다. (직관?)
[4. 구현]
#include <iostream>
#include <cstring>
char board[20+1][20+1];
// 좌상단에서 우하단으로 내려가는 구조이므로
// 기준점을 잡는 방식이 중요함.
// 현재 발견한 지점은 최소 위와 왼쪽은 검은 색이므로
// 블록을 놓을 수 있는 방법이 정해져 있음.
const int coverTypes[4][3][2] = {
{{0, 0}, {1, 0}, {0, 1}},
{{0, 0}, {0, 1}, {1, 1}},
{{0, 0}, {1, 0}, {1, 1}},
{{0, 0}, {1, 0}, {1, -1}}
};
bool canCover(const int H, const int W,
const int y, const int x, const int type)
{
for (int count = 0; count < 3; count++)
{
const int dy = y + coverTypes[type][count][0];
const int dx = x + coverTypes[type][count][1];
// 배열의 범위를 벗어나는 경우
if (dy < 0 || dy >= H || dx < 0 || dx >= W)
{
return false;
}
// 검은 칸과 겹치는 경우,
else if (board[dy][dx] == '#')
{
return false;
}
// 블록이 놓인 위치는 검은 칸으로 변경되어 있으므로
// 흰칸에는 아무 것도 놓여있지 않아서
// 블록을 위치 하게 할 수 있다.
}
return true;
}
// 배열을 순회하는 방식이 canCover() 와 같으므로
// 코드 재사용이 가능하도록 변경 할 수 있다.
void setCover(const int y, const int x, const int type, const char value)
{
for (int count = 0; count < 3; count++)
{
const int dy = y + coverTypes[type][count][0];
const int dx = x + coverTypes[type][count][1];
board[dy][dx] = value;
}
}
int cover(const int H, const int W)
{
// find white space
int y = -1; int x = -1;
for (int h = 0; h < H; h++)
{
for (int w = 0; w < W; w++)
{
if (board[h][w] == '.')
{
y = h;
x = w;
break;
}
}
if (y >= 0) break;
}
// BaseCase: 흰 공간이 없으므로 모두 채운 경우
if (y == -1 || x == -1)
{
return 1;
}
int ret = 0;
for (int type = 0; type < 4; type++)
{
// 현재 위치에서 블록을 놓을 수 있다면
if (canCover(H, W, y, x, type))
{
// 블록을 놓고
setCover(y, x, type, '#');
// 다음 경우에 대해서 확인 함
ret += cover(H, W);
// 이후 블록을 다시 놓기 위해 흰 칸으로 변경
setCover(y, x, type, '.');
}
}
return ret;
}
int main()
{
int TC;
int H, W;
scanf("%d", &TC);
for (int c = 0; c < TC; c++)
{
scanf("%d %d", &H, &W);
memset(board, 0, sizeof(board));
for (int d = 0; d < H; d++)
{
scanf("%s", board[d]);
}
// 흰칸의 개수를 먼저 체크 해 볼 수 있다.
printf("%d\n", cover(H, W));
}
}
'알고리즘 > Algospot' 카테고리의 다른 글
[분할 정복] QUADTREE (0) | 2022.02.05 |
---|---|
[완전 탐색] CLOCKSYNC (0) | 2022.02.05 |
[완전 탐색] PICNIC (0) | 2022.02.04 |
알고리즘 분석 (0) | 2022.02.04 |
알고리즘 문제 해결 전략 (0) | 2022.02.04 |