[알고리즘 문제해결 전략] 게임판

3 minute read

문제출처

  • [알고리즘 문제해결 전략1] - 6장 완전탐색 예제
  • 소스코드는 구현한 함수의 동작 여부를 관찰하기 위해 본인의 테스트 코드를 추가하여 작성한 결과이다.

문제 설명

  • H x W 크기의 게임판이 검은 칸, 흰 칸으로 구성되어 격자 모양을 하고 있음
  • 세 칸짜리 L자 모양의 블록으로 흰 칸을 덮으려고 함
  • 블록을 덮을 때 겹치거나, 검은 칸을 덮거나, 게임판 밖으로 나가면 안됨
  • 게임판이 주어질 때 덮는 방법의 수를 구하기
  • 흰 칸의 수는 50을 넘지 않음

문제 접근 방향 (Solution)

  • 흰 칸의 수가 3의 배수가 아닌 경우 덮을 수 없다.
  • 흰 칸에 4개의 L모양의 블록 중 하나를 덮고, 남은 흰 칸에 대해 재귀호출을 한다.
  • 블록을 놓는 순서는 중요하지 않으니, 일관된 블록을 놓는 방법을 정해야 중복으로 세는 문제를 막을 수 있다. 본 풀이에서는 가장 윗줄에서 왼쪽에 위치한 칸을 시작칸으로 설정한다.
  • 가능한 블록 모양을 배열에 담아두고 시작한다. (코드와 데이터를 분리한다.)

구현 결과


#include <iostream>
#include <vector>
#include <string>
using namespace std;

// [type][칸][상대좌표]
// type별로 각 칸의 상대좌표를 넣어둔다. 윗줄 가장 왼쪽에 위치하는 좌표를 (0,0) 기준좌표로 설정
const int coverType[4][3][2] = {
	{ {0, 0}, {1, 0}, {1, -1}, },
	{ {0, 0}, {0, 1}, {1, 1}, },
	{ {0, 0}, {1, 0}, {1, 1}, },
	{ {0, 0}, {1, 0}, {0, 1}, },
};

// board[r][c]에서 type번 방법으로 덮거나, 덮었던 블록을 없앤다.
// delta=1이면 덮고, delta=-1이면 블록을 없앤다.
// 블록을 덮을 수 없는 경우(범위를 벗어나거나, 검은칸을 덮거나, 이전에 놓은 블록과 겹치는 경우) false 반환
bool set(vector<vector<int>>& board, int r, int c, int type, int delta)
{
	bool ok = true;
	for (int i = 0; i < 3; i++) // 덮어야 하는 칸이 3개
	{
		int next_r = r + coverType[type][i][0]; // type번 블록의 i번 칸의 x좌표
		int next_c = c + coverType[type][i][1]; // type번 블록의 i번 칸의 y좌표
		if (next_r < 0 || next_r >= board.size() || next_c < 0 || next_c >= board[0].size()) // 범위를 벗어나는 경우
			ok = false;
        // delta=1(덮는 과정), 덮여 있지 않았던 칸 -> 1, 덮여 있던 칸 -> 2
        // delta=-1(빼는 과정), 덮여 있지 않았던 칸 -> -1, 덮여 있던 칸 -> 0
		else if ((board[next_r][next_c] += delta) > 1) 
			ok = false;
	}
	return ok;
}

// board의 모든 빈 칸을 덮을 수 있는 방법의 수를 반환한다.
// board[i][j]=1 이면 덮인 칸 혹은 검은 칸
// board[i][j]=0 이면 아직 덮이지 않은 칸
int cover(vector<vector<int>>& board)
{
	// 아직 채우지 못한 칸 중 가장 윗줄 왼쪽에 위치한 좌표를 찾는다.
	int r = -1, c = -1;
	for (int i = 0; i < board.size(); i++)
	{
		for (int j = 0; j < board[i].size(); j++)
		{
			if (board[i][j] == 0)
			{
				r = i;
				c = j;
				break;
			}
		}
		if (r != -1) break;
	}
	// 기저 사례: 모든 칸을 채웠으면 1을 반환한다.
	if (r == -1) return 1;
	int ret = 0;
	for (int type = 0; type < 4; type++)
	{
		// 만약 bound[r][c]를 type형태로 덮을 수 있으면 재귀호출한다.
		if (set(board, r, c, type, 1))
			ret += cover(board);
		// 덮었던 블록을 치운다.
		set(board, r, c, type, -1);
	}
	return ret;
}

int main()
{
	int C;
	cin >> C;
	for (int test_case = 0; test_case < C; test_case++)
	{
		int H, W;
		int white_num = 0;
		vector<vector<int>> board;
		string str;
		cin >> H >> W;
		for (int i = 0; i < H; i++)
		{
			cin >> str;
			vector<int> v;
			for (auto ele : str)
			{
				if (ele == '#') // 검은 칸
					v.push_back(1);
				else if (ele == '.') // 흰 칸
				{
					v.push_back(0);
					white_num += 1;// 흰 칸 개수 세기
				}
			}
			board.push_back(v);
		}
		int ans = 0;
		if (white_num % 3) // 흰 칸의 개수가 3의 배수가 아니면 탐색할 필요 x
			cout << ans << endl;
		else
		{
			ans = cover(board);
			cout << ans << endl;
		}
	}
	return 0;
}

Test Case

input

3
3 7
#.....#
#.....#
##...##
3 7
#.....#
#.....#
##..###
8 10
##########
#........#
#........#
#........#
#........#
#........#
#........#
##########

output

0
2
1514

Categories:

Updated:

Leave a comment