쩡으니

[백준] 다리만들기 2 - 17472 (Java)

17825

이번 문제는 bfs 와 크루스칼 알고리즘을 이용하여 풀이를 하였다. 크루스칼 알고리즘은 [프로그래머스]섬 연결하기에 설명을 참고하길 바란다.

boolean visited[][] = new boolean[N][M];
int islandNum = 1;
Queue<Info> q = new LinkedList<>();
for (int i = 0; i < N; i++) {
	for (int j = 0; j < M; j++) {
		if (arr[i][j] == 1 && !visited[i][j]) { //arr 는 입력 받은 값을 저장해둔 배열이다.
			q.clear();
			q.offer(new Info(i, j));
			visited[i][j] = true;
			arr[i][j] = islandNum;
			getNum(islandNum, q, visited);
			islandNum++;
		}
	}
}
private static void getNum(int islandNum, Queue<Info> q, boolean[][] visited) {
	int tx = 0, ty = 0;
	while (!q.isEmpty()) {
		Info temp = q.poll();
		for (int i = 0; i < 4; i++) {
			tx = temp.j + dx[i];
			ty = temp.i + dy[i];
			if (tx < 0 || ty < 0 || tx >= M || ty >= N)
				continue;
			if (arr[ty][tx] == 0)
				continue;
			if (visited[ty][tx])
				continue;
			arr[ty][tx] = islandNum;
			visited[ty][tx] = true;
			q.offer(new Info(ty, tx));
		}
	}
}

해당 과정을 통해 그림처럼 각 색칠된 부분 즉 섬의 영역에 번호를 매겨 각 섬마다 구분이 되게 해두었다. 파란색은 1번 초록색은 2번 노란색은 3번 빨간색은 4번 이런식이다. for 문을 돌면서 입력 받은 배열의 값이 1이면 섬 영역이고 방문하지 않은 곳이면 새로운 섬이기에 해당 좌표를 큐에 넣고 bfs를 시작한다. 0이면 탐색하지 않는 식으로 하여 붙어있는 1을 모두 조사해 섬 번호를 붙여 주며 방문표시를 해준다.

visited = new boolean[N][M];
List<Bridge> list = new ArrayList<>();
for (int i = 0; i < N; i++) {
	for (int j = 0; j < M; j++) {
		if (arr[i][j] != 0 && !visited[i][j]) {
			q.clear();
			q.offer(new Info(i, j));
			visited[i][j] = true;
			getBridge(arr[i][j], q, visited, list);
		}
	}
}
private static void getBridge(int islandNum, Queue<Info> q, boolean[][] visited, List<Bridge> list) {
	int tx = 0, ty = 0;
	while (!q.isEmpty()) {
		Info temp = q.poll();
		for (int i = 0; i < 4; i++) {
			tx = temp.j + dx[i];
			ty = temp.i + dy[i];
			if (tx < 0 || ty < 0 || tx >= M || ty >= N)
				continue;
			if (visited[ty][tx])
				continue;
			if (arr[ty][tx] == islandNum) {
				visited[ty][tx] = true;
				q.offer(new Info(ty, tx));
				continue;
			}

			if (arr[ty][tx] == 0) {
				int cost = 1;
				while (true) {
					tx = tx + dx[i];
					ty = ty + dy[i];
					if (tx < 0 || ty < 0 || tx >= M || ty >= N)
						break;
					if (arr[ty][tx] == islandNum)
						break;
					if (arr[ty][tx] != 0 && arr[ty][tx] != islandNum) {
						if (cost < 2)
							break;
						list.add(new Bridge(islandNum, arr[ty][tx], cost));
						break;
					}
					cost++;
				}
			}
		}
	}
}

섬 번호를 다 매겼으면 이제 다리를 놓아준다. for 문을 돌면서 0이 아니면 섬이란 뜻이고 방문하지 않았으면 다리를 안 놓았다는 뜻이므로 해당 좌표를 q에 넣고 다리 놓는 bfs를 시작한다. bfs 를 하면서 자기 자신의 섬 번호면 내 옆칸이라는 뜻이고 내 옆칸에서도 다리를 놓아봐야하기 때문에 q에 넣어준다. 만약 0 이면 다리를 놓을 수 있는 지 확인을 해준다. 문제에서 다리는 직진으로만 놓을 수 있기 때문에 조금 더 쉽게 풀 수 있었다. 0인 경우 다른 섬 번호를 만날 때 까지 while 문을 돌며 한칸한칸 이동해준다. 이때 U이렇게 생긴 섬이 존재하는 예제가 있기 때문에 내 섬번호를 만났으면 더이상 가면 안되기에 break를 해준다. 또한 문제에서 비용이 2 이하인 다리는 지을 수 없기에 break를 해준다. 만약 모든 조건이 충족하면 Bridge 리스트에 넣어준다.

다리를 다 만든 이후에 크루스칼 알고리즘을 시작하면 된다.

int unioncount = 0;
for (int i = 1; i < islandNum; i++) {
	if (parent[i] == i)
		unioncount++;
}
if (unioncount != 1)
	cost = 0;
if (bridgecnt != islandNum - 2)
	cost = 0;

크루스칼 알고리즘은 모든 노드들이 연결되어야 하므로 하나라도 연결되지 않은 섬이 있다면 -1을 출력해준다. 또한 간선의 개수는 노드 - 1 개 여야 하므로 해당 조건을 걸어준다. 여기서 islandNum은 노드 + 1로 되어 있기 때문에 - 2를 해주었다.

출처: 백준 https://www.acmicpc.net/problem/17472

comments powered by Disqus