[BOJ 17472] 다리만들기2

업데이트:

문제

  • BOJ 17472
  • 문제의 저작권은 Baekjoon Online Judge에 있습니다.

접근방식

먼저 입력값을 map[][]에 담고 for문을 돌면서 땅마다 각 섬의 번호를 붙여준다. 이때 BFS를 사용해서 각 섬에 있는 땅 좌표들을 찾아주고 리스트lands에 저장한다.

코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

//다리 만들기2(BFS+프림)
public class Main {
	static final int MAX_LAND = 6; // 최대 섬의 개수
	static int N, M;
	static int map[][];
	static boolean visited[][]; // 해당 땅에 방문했는지 체크
	static ArrayList<Land>[] islands; // 각 섬에서 모든 땅의 좌표들
	static ArrayList<Bridge>[] bridges; // 각 섬에서 다른 섬까지 놓은 최소 다리 길이
	static int[] di = { -1, 0, 1, 0 }; // 우, 상, 하, 좌
	static int[] dj = { 0, 1, 0, -1 };
	static int islandNum; // 섬개수
	static int Ans; // 최소 다리 길이

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		N = Integer.parseInt(st.nextToken()); // 세로 크기(행)
		M = Integer.parseInt(st.nextToken()); // 가로 크기(열)

		map = new int[N + 1][M + 1];
		visited = new boolean[N + 1][M + 1];

		for (int i = 1; i <= N; i++) {
			st = new StringTokenizer(br.readLine());
			for (int j = 1; j <= M; j++) {
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}

		islands = new ArrayList[MAX_LAND + 1];

		// 섬번호 표시하기
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= M; j++) {
				// 새로운 땅을 만나면 BFS로 인접 좌표 찾기
				if (!visited[i][j] && map[i][j] == 1) {
					// 해당 땅에 섬번호를 적어주고
					++islandNum;
					map[i][j] = islandNum;
					// 이 섬에 있는 모든 땅의 좌표 구하기
					ArrayList<Land> lands = bfs(i, j, islandNum);
					islands[islandNum] = new ArrayList<>();
					islands[islandNum].addAll(lands);
				}
			}
		}

		bridges = new ArrayList[islandNum + 1]; // 1번섬부터 시작
		for (int i = 1; i <= islandNum; i++)
			bridges[i] = new ArrayList<>();

		// 각 섬들의 좌표를 돌면서 다른 섬으로의 다리 만들기
		for (int i = 1; i <= islandNum; i++) {
			ArrayList<Land> lands = islands[i]; // 섬 가져오기

			for (int j = 0; j < lands.size(); j++) { // 이 섬의 땅들 가져오기
				Land l = lands.get(j);

				// 주변이 다리를 만들 수 있는 상황인지 확인
				for (int d = 0; d < 4; d++) {
					int nx = l.x + di[d];
					int ny = l.y + dj[d];

					if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && map[nx][ny] == 0) {
						makeBridge(i, nx, ny, d); // 다리 시작점으로 두고 다리 만들기
					}
				}
			}
		}
		makeMST(); // 최소신장트리 생성
		System.out.println(Ans);
	}

	//섬번호 표시함
	private static ArrayList<Land> bfs(int i, int j, int num) {
		ArrayList<Land> lands = new ArrayList<>(); // 현재 섬에 있는 땅의 좌표
		Queue<Land> queue = new LinkedList<>();
		queue.offer(new Land(i, j));
		lands.add(new Land(i, j));
		visited[i][j] = true; // 방문 표시

		while (!queue.isEmpty()) {
			Land l = queue.poll();
			for (int d = 0; d < 4; d++) {
				int nx = l.x + di[d];
				int ny = l.y + dj[d];

				if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && !visited[nx][ny] && map[nx][ny] == 1) {
					visited[nx][ny] = true; // 방문표시
					map[nx][ny] = num; // 섬번호 표시
					Land newl = new Land(nx, ny);
					queue.offer(newl);
					lands.add(newl);
				}
			}
		}
		return lands;
	}

	//각 섬의 땅에서 다리 놓기
	private static void makeBridge(int num, int x, int y, int d) { // 시작 섬 번호, x좌표, y좌표, 다리 길이
		int nx = x, ny = y;
		int dist = 1; // 다리의 길이

		while (true) {
			// 계속 다리를 놓자
			nx += di[d];
			ny += dj[d];

			if (nx < 1 || nx > N || ny < 1 || ny > M)
				break;

			if (map[nx][ny] != 0) { // 다른 섬을 만나고 다리 길이가 2이상이면
				if(dist >= 2) 
					bridges[num].add(new Bridge(map[nx][ny], dist));
				break;
			}
			dist++;
		}
	}

	//모든 섬을 연결하는 최소 다리길이 구함
	private static void makeMST() {
		PriorityQueue<Bridge> pq = new PriorityQueue<>(); // 우선순위큐 생성
		boolean[] visited = new boolean[islandNum + 1];
		int now = 1; // 섬 1번부터 출발
		visited[now] = true; // 신장트리 넣기

		// 모든 섬 돌기, 이미 방문한 섬으로 돌아가지x
		for (int cnt = 1; cnt < islandNum; cnt++) {
			ArrayList<Bridge> nowBridges = bridges[now]; // 현재 섬에서 연결된 다리 정보
			for (Bridge b : nowBridges) {
				if (!visited[b.num]) {// 연결된 섬이 아직 신장트리가 아니면
					pq.add(b); // 후보 다리로 추가
				}
			}

			while (!pq.isEmpty()) {
				Bridge bridge = pq.poll(); // 최소 간선 정점
				if (!visited[bridge.num]) { // 아직 방문하지 않은 섬이라면
					visited[bridge.num] = true; // 방문 처리
					Ans += bridge.dist; // 최소 가중치 누적 합
					now = bridge.num; // 이제 다음 섬으로 두기
					break;
				}
			}
		}
		
		//하나라도 방문 안한 섬이 있으면 -1출력
		for(int i=1; i<=islandNum; i++) {
			if(!visited[i]) {
				Ans = -1;
				break;
			}
		}
	}

	// 땅 좌표
	static class Land { 
		int x;
		int y;

		public Land(int x, int y) {
			this.x = x;
			this.y = y;
		}
	}

	// 현재 섬에서 다리 정보
	static class Bridge implements Comparable<Bridge> { 
		int num;
		int dist;

		public Bridge(int num, int dist) {
			this.num = num;
			this.dist = dist;
		}

		@Override
		public int compareTo(Bridge o) {
			return Integer.compare(this.dist, o.dist);
		}
	}
}