Outdated/Algorithm Solution

[BOJ] 2665번 미로만들기

해달 2019. 5. 27. 09:00

문제

n×n 바둑판 모양으로 총 n2개의 방이 있다. 일부분은 검은 방이고 나머지는 모두 흰 방이다. 검은 방은 사면이 벽으로 싸여 있어 들어가 수 없다. 서로 붙어 있는 두 개의 흰 방 사이에는 문이 있어서 지나다닐 수 있다. 윗줄 맨 왼쪽 방은 시작방으로서 항상 흰 방이고, 아랫줄 맨 오른쪽 방은 끝방으로서 역시 흰 방이다.

시작방에서 출발하여 길을 찾아서 끝방으로 가는 것이 목적인데, 아래 그림의 경우에는 시작방에서 끝 방으로 갈 수가 없다. 부득이 검은 방 몇 개를 흰 방으로 바꾸어야 하는데 되도록 적은 수의 방의 색을 바꾸고 싶다.

아래 그림은 n=8인 경우의 한 예이다.

위 그림에서는 두 개의 검은 방(예를 들어 (4,4)의 방과 (7,8)의 방)을 흰 방으로 바꾸면, 시작방에서 끝방으로 갈 수 있지만, 어느 검은 방 하나만을 흰 방으로 바꾸어서는 불가능하다. 검은 방에서 흰 방으로 바꾸어야 할 최소의 수를 구하는 프로그램을 작성하시오.

단, 검은 방을 하나도 흰방으로 바꾸지 않아도 되는 경우는 0이 답이다.

입력

첫 줄에는 한 줄에 들어가는 방의 수 n(1≤n≤50)이 주어지고, 다음 n개의 줄의 각 줄마다 0과 1이 이루어진 길이가 n인 수열이 주어진다. 0은 검은 방, 1은 흰 방을 나타낸다.

출력

첫 줄에 흰 방으로 바꾸어야 할 최소의 검은 방의 수를 출력한다.

예제

// 입력
8
11100110
11010010
10011010
11101100
01000111
00110001
11011000
11000111

// 출력
2

코드

// cpp 17

// 이 문제를 푸는 방법은 2가지가 있다.
// 먼저, 다익스트라 알고리즘을 이용하는 방법이다.
// 다익스트라 알고리즘를 모른다면 맨 밑의 링크를 참고하길 바란다.
#include <iostream>
#include <string>
#include <utility>
#include <functional>
#include <queue>

using namespace std;

using pos = pair<int, int>;
using pqelem = pair<pos, int>;

int N, dist[50][50];
int dr[] = { -1, 1, 0, 0 }, dc[] = { 0, 0, -1, 1 };
int map[50][50];
priority_queue<pqelem> pq;

int main()
{
	scanf("%d", &N);
	for (int r = 0; r < N; r++)
	{
		for (int c = 0; c < N; c++)
		{
			scanf("%1d", &map[r][c]);
			// 각각의 방들을 vertex라고 하면, 각 값들은 weight라고 볼 수 있다.
			// 해당 문제는 흰 방을 1, 검은 방을 0으로 준다.
			// 원하는 방향으로 동작시키려면 이 값을 반전시켜야 한다.
			map[r][c] = (map[r][c] == 0) ? 1 : 0;
			dist[r][c] = 987654321;
		}
	}
	dist[0][0] = 0;

	pq.push({ {0, 0}, 0 });
	while (!pq.empty())
	{
		// std::priority_queue의 기본 정렬은 내림차순이다. (<)
		// 기본적으로 끝점 방향으로 나아가야 하기 때문에
		// 오른쪽 혹은 아래쪽으로 나아가야 한다.
		int r = pq.top().first.first;
		int c = pq.top().first.second;
		int dis = -pq.top().second; // 이 값은 추후 설명한다.
		pq.pop();

		// dis가 현재 저장되어 있는 값보다 크다면
		// 확인할 필요가 없다.
		if (dis > dist[r][c])
			continue;

		// 상하좌우를 탐색한다.
		for (int i = 0; i < 4; i++)
		{
			int nr = r + dr[i];
			int nc = c + dc[i];

			if (nr < 0 || nr >= N || nc < 0 || nc >= N)
				continue;

			// 새 값이 더 작다면 갱신시켜야 한다.
			int newDis = dis + map[nr][nc];
			if (newDis < dist[nr][nc])
			{
				dist[nr][nc] = newDis;
				// 여기서 음수로 값을 저장하는 이유는
				// 검은방을 택하기 보단 흰방을 택하기 위해서이다.
				pq.push({ { nr, nc }, -newDis });
			}
		}
	}

	cout << dist[N - 1][N - 1];
}

// 두번째 방법은 DFS를 할때마다 갱신시키는 것이다.
#include <iostream>
#include <string>

using namespace std;

string map[50];
int N, cnt;
int dr[] = { -1, 1, 0, 0 }, dc[] = { 0, 0, -1, 1 };
bool hasEnded, isVisited[50][50];

void dfs(int r, int c)
{
	// 목표에 도달했다면 더이상 DFS를 하지 않는다.
	if (r == N - 1 && c == N - 1)
	{
		hasEnded = true;
		return;
	}

	isVisited[r][c] = true;

	for (int i = 0; i < 4; i++)
	{
		int nr = r + dr[i];
		int nc = c + dc[i];

		if (nr < 0 || nr >= N || nc < 0 || nc >= N)
			continue;

		if (map[nr][nc] == '1' && !isVisited[nr][nc])
			dfs(nr, nc);
	}
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> N;
	for (int i = 0; i < N; i++)
		cin >> map[i];

	while (true)
	{
		dfs(0, 0);
		if (hasEnded)
			break;

		++cnt;
		for (int r = 0; r < N; r++)
		{
			for (int c = 0; c < N; c++)
			{
				// 방문한 흰 방들 중 주변의 검은방을 뚫는다.
				if (map[r][c] == '1' && isVisited[r][c])
				{
					for (int i = 0; i < 4; i++)
					{
						int nr = r + dr[i];
						int nc = c + dc[i];

						if (nr < 0 || nr >= N || nc < 0 || nc >= N)
							continue;

						if (map[nr][nc] == '0')
							map[nr][nc] = '1';
					}
				}
			}
		}

		memset(isVisited, false, sizeof(isVisited));
	}

	cout << cnt;
}

 

 

 

'Outdated > Algorithm Solution' 카테고리의 다른 글

[BOJ] 10835번 카드게임  (0) 2019.05.29
[BOJ] 2098번 외판원 순회  (0) 2019.05.28
[BOJ] 9020번 골드바흐의 추측  (0) 2019.05.26
[BOJ] 11067번 모노톤길  (0) 2019.05.25
[BOJ] 3036번 링  (0) 2019.05.24