알고리즘/PS

[PS] 백준 17472 : 다리 만들기 2

Dev-SeeOne 2020. 5. 7. 09:58

문제

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

 

17472번: 다리 만들기 2

첫째 줄에 지도의 세로 크기 N과 가로 크기 M이 주어진다. 둘째 줄부터 N개의 줄에 지도의 정보가 주어진다. 각 줄은 M개의 수로 이루어져 있으며, 수는 0 또는 1이다. 0은 바다, 1은 땅을 의미한다.

www.acmicpc.net

해설

 

다리만들기 문제는 BFSMST 을 만드는 알고리즘을 이용해서 문제를 해결 할 수 있습니다. MST를 만드는 알고리즘에 대한 설명은 다음 블로그 링크를 참조해주세요. 

 

문제는 크게 두 부분으로 나뉘어 집니다. 

 

1. 바다를 제외한 연결된 영역을 같은 섬 영역으로 표시하고 각 섬마다 구분 지어 주기

 

2. 각 섬에서 다른 섬까지 이어지는 경로 찾기

 

1번의 경우 일반적인 BFS를 통해 해결할 수 있습니다. 2번의 경우 모든 방향을 고려할 필요 없이 한 정점에서 오른쪽으로 탐색하는 방향은 그와 만나는 다른 정점에서 왼쪽으로 탐색하는 경우와 결과가 같으므로 각 정점에서 오른쪽과 아래쪽으로 이동하는 경우만 고려하여 문제를 해결할 수 있습니다.

 

위의 두 가지 경우를 모두 처리한다면 Kruskal 이나 Prim 알고리즘을 사용해서 문제를 쉽게 해결할 수 있습니다. 

 

코드

/** 
* 알고리즘 : kruskal
*/
public class BOJ_17472_다리_만들기_2 {
	static StringBuilder sb = new StringBuilder();
	static int n, m;
	static int[][] map;
	static int[] group;
	static boolean[][] visit;
	static int[] dx = {-1, 0, 1, 0};
	static int[] dy = {0, 1, 0, -1};
	
	
	static class Edge implements Comparable<Edge>{
		int u, v, c;

		public Edge(int u, int v, int c) {
			this.u = u;
			this.v = v;
			this.c = c;
		}

		@Override
		public int compareTo(Edge e) {
			// TODO Auto-generated method stub
			return this.c - e.c;
		}
	}
	
	static int find(int u) {
		if(group[u] < 0) return u;
		return group[u] = find(group[u]);
	}
	
	static boolean union(int u, int v) {
		u = find(u); v = find(v);
		if(u == v) return false;
		group[v] = u;
		return true;
	}
	
	public static void main(String[] args) throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;
	
		st = new StringTokenizer(br.readLine());
		n = Integer.parseInt(st.nextToken());
		m = Integer.parseInt(st.nextToken());
		
		map = new int[n][m];
		visit = new boolean[n][m];
		group = new int[7];
		Arrays.fill(group, -1);
		
		for(int i = 0; i<n; i++) {
			st = new StringTokenizer(br.readLine());
			for(int j = 0; j<m; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		
		int color = 0;
		// 인접한 영역을 같은 섬으로 표시한다.
		Queue<int[]> q = new LinkedList<>();
		for(int i = 0; i<n; i++) {
			for(int j = 0; j<m; j++) {
				if(visit[i][j] || map[i][j] <= 0) continue;
				color += 1;
				visit[i][j] = true;
				q.add(new int[] {i, j});
				while(!q.isEmpty()) {
					int[] pos = q.poll();
					map[pos[0]][pos[1]] = color;
					for(int dir = 0; dir<4; dir++) {
						int nx = pos[0] + dx[dir];
						int ny = pos[1] + dy[dir];
						if(nx < 0 || nx >= n || ny < 0 || ny >= m || visit[nx][ny] || map[nx][ny] <= 0) continue;
						visit[nx][ny] = true;
						q.add(new int[] {nx, ny});
					}
				} // while()
			} // for(j)
		} // for(i)
		
		// 한 좌표에서 이동할 수 있는 방향은 중복을 제거하기 위해서 오른쪽과 아래 방향만 고려하면 되므로 
		// 각각의 좌표에서 오른쪽과 아래쪽으로 이동했을 때 다른 섬이 발견되는 경우 이 때 거리가 1 보다 클 경우
		// 각각의 섬의 번호를 정점으로 하여금 간선 정보를 우선순위 큐에 업데이트 한다.
		PriorityQueue<Edge> pq = new PriorityQueue<>();
		for(int i = 0; i<n; i++) {
			for(int j = 0; j<m; j++) {
				if(map[i][j] > 0) {
					for(int r = i + 1; r < n; r++) {
						if(map[r][j] > 0) {
							if(map[i][j] != map[r][j] && r - i - 1 > 1)
								pq.add(new Edge(map[i][j], map[r][j], r - i - 1));
							break;
						}
					}
					
					for(int c = j + 1; c < m; c++) {
						if(map[i][c] > 0) {
							if(map[i][j] != map[i][c] && c - j - 1 > 1) 
								pq.add(new Edge(map[i][j], map[i][c], c - j - 1));
							break;
						}
					}
				}
			} // for(j)
		} // for(i)
		
		int edge = 0;
		int cost = 0;
		// 크루스칼 알고리즘 수행
		while(!pq.isEmpty()) {
			if(edge == color - 1) break;
			Edge e = pq.poll();
			if(union(e.u, e.v)) {
				cost += e.c;
				edge += 1;
			}
		}
		System.out.println((edge == color - 1 ? cost : -1));
	}
}