백준 문제풀이

BOJ 2146 다리 만들기

Seonyoung Jeong 2021. 8. 15. 20:41

📎Problem Link

https://www.acmicpc.net/problem/2146

 

2146번: 다리 만들기

여러 섬으로 이루어진 나라가 있다. 이 나라의 대통령은 섬을 잇는 다리를 만들겠다는 공약으로 인기몰이를 해 당선될 수 있었다. 하지만 막상 대통령에 취임하자, 다리를 놓는다는 것이 아깝다

www.acmicpc.net

 

📋 문제 정리

  • 그래프에 1로 육지를, 0으로 바다를 나타냄. (그래프는 N X N)
  • 1이 여러개 뭉쳐있는 곳을 섬 하나로 봄
  • 한 섬과 다른 섬을 잇는 가장 짧은 다리 찾기
  • 첫 줄에는 지도의 크기 N 입력 (N은 100 이하)
  • 그 다음 줄부터 N개의 줄에는 0과 1로 바다와 육지를 입력함.

📌 예제 1

 

예제를 바탕으로 섬을 나누고 섬에 각각의 번호를 부여하면 위와 같다.  위의 예제에 대한 답은 3이며, 위의 빨간 색 표시가 최소로 연결할 수 있는 다리의 예시 중 하나이다.

 

🧐 Idea

  • 기존에 0과 1로 바다와 육지만 나타나있는 map에서 각 섬마다 다른 번호를 부여해야함.
  • 각 섬을 시작으로 DFS/BFS를 돌려 다른 번호의 섬을 만나면 즉시 종료.
  • 각 육지에서 시작하여 연결한 다리 수 중 최솟값을 골라 return 함.

💻 Code

int main() {
	map bridge;
	int num = 1, result=10000;
	cin >> bridge.N;

	for (int i = 0; i < bridge.N; i++) {
		for (int j = 0; j < bridge.N; j++) {
			cin >> bridge.board[i][j];
		}
	} // 섬 입력

	for (int i = 0; i < bridge.N; i++) {
		for (int j = 0; j < bridge.N; j++) {
			if (!bridge.visited[i][j] && bridge.board[i][j])
				bridge.Area(i, j, num++);
		}
	} // 각 섬마다 번호 다르게 하기

	for (int i = 1; i < num; i++) {
		memset(bridge.visited, false, sizeof(bridge.visited));
		result=min(result, bridge.BFS(i));
	}

	cout << result << endl;
}

main 문은 쉬우니 가볍게 살펴보도록 하자. 위의 주석에서 볼 수 있다시피, 첫번째 이중 for문을 통해 board를 입력하고 두번째 이중 for문을 통해 각 섬마다 번호를 다르게 한다. 세번째 for문으로 BFS를 실행시켜 최소 다리를 구한다. 이 때 for문의 실행 범위는 1부터 num-1까지인데, 그 이유는 몇번에서 시작을 하든 num번 섬까지 가는 루트는 이미 탐색됐을 것이기 때문이다.

 

void Area(int y, int x, int num) {
		board[y][x] = num;
		visited[y][x] = true;
		for (int i = 0; i < 4; i++) {
			int nx = x + mx[i];
			int ny = y + my[i];

			if (nx < 0 || nx >= N || ny < 0 || ny >= N) continue; //다음 좌표의 범위가 표를 벗어나면 다른 좌표로 변경

			if (!visited[ny][nx] && board[ny][nx]) //다음 좌표가 방문한 적이 없는 육지이면
			{
				Area(ny, nx, num); //다음 좌표 육지로 옮겨가기
			}
		}
	}

int BFS(int areanum) {
	queue <pair<int, int>> q;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < N; j++) {
			if (board[i][j] == areanum) {
				visited[i][j] = true;
				q.push(make_pair(i, j));
			}
		}
	}
		int result = 0;
		while (!q.empty()) {
		int queuesize = q.size();
			for (int i = 0; i < queuesize; i++) {
			int y = q.front().first;
			int x = q.front().second;
			q.pop();
				for (int i = 0; i < 4; i++) {
				int ny = y + my[i];
				int nx = x + mx[i];
					if (ny < 0 || ny >= N || nx < 0 || nx >= N) continue;
					if (board[ny][nx] && board[ny][nx] != areanum)
					return result;
				else if (!visited[ny][nx] && !board[ny][nx]) { //방문한 적 없는 바다면
					visited[ny][nx] = true;
					q.push(make_pair(ny, nx));
				}
			}
		}
		result++;
	}
}

먼저 Area함수를 보자. Area함수가 바로 각 섬마다 번호를 매기는 역할을 한다. 섬 번호는 main 문으로 부터 넘겨받아 섬 하나를 다 훑으면서 1을 섬 번호로 바꾼다. 

다음은 이 문제의 핵심인 BFS 함수이다. 먼저 area번에 해당하는 칸들을 queue에 모두 넣는다. 그 후 queue가 빌 때까지 현재 칸의 상하좌우를 살핀다. 만약 보고자 하는 칸이 다른 섬일 때, 한 섬까지 가는 다리를 이은 것이기 때문에 더 볼 필요가 없으므로 현재 result 값을 리턴한다. 방문한 적 없는 바다라면 queue에 추가한다. while문이 끝나기 전, 현재 칸에 다리를 만들었다는 뜻으로 result++을 실행한다.


이번 문제는 골드3인만큼 머리가 터질 것 같았다. Idea도 차근차근 잘 짰고, 코드도 차근차근 잘 짰는데 계속 에러가 나고 코드가 안 돌아서 몇일을 헤맸던 문제다. 결국 구글에서 search를 해서 풀 수 있었다. 사실 BFS에서 조금만 더 깊게 나아가면 풀 수 있었던 것 같은데 현재 내 머리의 한계였던 것 같다. 이 한계를 뿌시기 위해 더 심화된 BFS/DFS문제를 풀어봐야겠다.