Article Image
Article Image
read

문제

섬으로 이루어진 나라가 있고, 모든 섬을 다리로 연결하려고 한다. 이 나라의 지도는 N×M 크기의 이차원 격자로 나타낼 수 있고, 격자의 각 칸은 땅이거나 바다이다.

섬은 연결된 땅이 상하좌우로 붙어있는 덩어리를 말하고, 아래 그림은 네 개의 섬으로 이루어진 나라이다. 색칠되어있는 칸은 땅이다.

다리는 바다에만 건설할 수 있고, 다리의 길이는 다리가 격자에서 차지하는 칸의 수이다. 다리를 연결해서 모든 섬을 연결하려고 한다. 섬 A에서 다리를 통해 섬 B로 갈 수 있을 때, 섬 A와 B를 연결되었다고 한다. 다리의 양 끝은 섬과 인접한 바다 위에 있어야 하고, 한 다리의 방향이 중간에 바뀌면 안된다. 또, 다리의 길이는 2 이상이어야 한다.

다리의 방향이 중간에 바뀌면 안되기 때문에, 다리의 방향은 가로 또는 세로가 될 수 밖에 없다. 방향이 가로인 다리는 다리의 양 끝이 가로 방향으로 섬과 인접해야 하고, 방향이 세로인 다리는 다리의 양 끝이 세로 방향으로 섬과 인접해야 한다.

섬 A와 B를 연결하는 다리가 중간에 섬 C와 인접한 바다를 지나가는 경우에 섬 C는 A, B와 연결되어있는 것이 아니다.

아래 그림은 섬을 모두 연결하는 올바른 2가지 방법이고, 다리는 회색으로 색칠되어 있다. 섬은 정수, 다리는 알파벳 대문자로 구분했다.

다리의 총 길이: 13
D는 2와 4를 연결하는 다리이고, 3과는 연결되어 있지 않다.
다리의 총 길이: 9 (최소)

다음은 올바르지 않은 3가지 방법이다

C의 방향이 중간에 바뀌었다 D의 길이가 1이다. 가로 다리인 A가 1과 가로로 연결되어 있지 않다.

다리가 교차하는 경우가 있을 수도 있다. 교차하는 다리의 길이를 계산할 때는 각 칸이 각 다리의 길이에 모두 포함되어야 한다. 아래는 다리가 교차하는 경우와 기타 다른 경우에 대한 2가지 예시이다.

A의 길이는 4이고, B의 길이도 4이다.
총 다리의 총 길이: 4 + 4 + 2 = 10
다리 A: 2와 3을 연결 (길이 2)
다리 B: 3과 4를 연결 (길이 3)
다리 C: 2와 5를 연결 (길이 5)
다리 D: 1과 2를 연결 (길이 2)
총 길이: 12

나라의 정보가 주어졌을 때, 모든 섬을 연결하는 다리 길이의 최솟값을 구해보자.

입력

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

출력

모든 섬을 연결하는 다리 길이의 최솟값을 출력한다. 모든 섬을 연결하는 것이 불가능하면 -1을 출력한다.

제한

  • 1 ≤ N, M ≤ 10
  • 3 ≤ N×M ≤ 100
  • 2 ≤ 섬의 개수 ≤ 6

예제 입력 1

7 8
0 0 0 0 0 0 1 1
1 1 0 0 0 0 1 1
1 1 0 0 0 0 0 0
1 1 0 0 0 1 1 0
0 0 0 0 0 1 1 0
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1

예제 출력 1

9

예제 입력 2

7 8
0 0 0 1 1 0 0 0
0 0 0 1 1 0 0 0
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1

예제 출력 2

10

예제 입력 3

7 8
1 0 0 1 1 1 0 0
0 0 1 0 0 0 1 1
0 0 1 0 0 0 1 1
0 0 1 1 1 0 0 0
0 0 0 0 0 0 0 0
0 1 1 1 0 0 0 0
1 1 1 1 1 1 0 0

예제 출력 3

9

예제 입력 4

7 7
1 1 1 0 1 1 1
1 1 1 0 1 1 1
1 1 1 0 1 1 1
0 0 0 0 0 0 0
1 1 1 0 1 1 1
1 1 1 0 1 1 1
1 1 1 0 1 1 1

예제 출력 4

-1

풀이

이 문제는 bfs(너비 우선 탐색), dfs(깊이 우선 탐색), kruskal(크루스칼) 알고리즘을 이용해서 풀 수 있었다. 3단계로 나눠서 풀 수 있는데 첫번째로 DFS를 이용해서 각 섬에 번호를 매기고 두번째로는 BFS를 이용해서 거리를 계산한 뒤 에 크루스칼을 이용해서 답을 구했다.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;

public class Main {
    static int[] dx = {-1, 1, 0, 0};
    static int[] dy = {0, 0, -1, 1};
    static int N;       //최대 행
    static int M;       //최대 열
    static int[] parent;    //크루스칼 알고리즘을 위한 부모 배열
    static int[][] map;     //전체 좌표 배열
    static boolean[][] visited;     //라벨링에 사용할 방문 배열
    static Queue<Pair> queue = new LinkedList<>();  //섬에 포함되는 모든 좌표를 위한 큐
    static PriorityQueue<Pair> pq = new PriorityQueue<>();  //크루스칼 알고리즘에 사용할 우선순위 큐

    public static void main(String[] args) throws Exception{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] input = br.readLine().split(" ");
        N = Integer.parseInt(input[0]);
        M = Integer.parseInt(input[1]);
        map = new int[N][M];
        visited = new boolean[N][M];

        for(int i=0; i<N; i++) {
            input = br.readLine().split(" ");

            for(int j=0; j<M; j++) {
                map[i][j] = Integer.parseInt(input[j]);
            }
        }

        int island = 0;

        for(int i=0; i<N; i++) {        //라벨링
            for(int j=0; j<M; j++) {
                if(map[i][j]==1 && !visited[i][j]) {
                    visited[i][j] = true;
                    map[i][j] = island+1;
                    dfs(i, j, island+1);
                    island++;
                    queue.add(new Pair(i, j, map[i][j], -1, 0));
                }
            }
        }

        while(!queue.isEmpty()) {       //각 섬간의 거리 계산
            bfs(queue.poll(), island);
        }

        parent = new int[island+1];
        for(int i=1; i<=island; i++)
            parent[i] = i;

        int ans = 0;
        int cnt = 0;                //모든 섬 연결 가능한지 체크하기 위한 변수

        while(!pq.isEmpty()) {      //크루스칼 알고리즘 진행
            Pair temp = pq.poll();

            int x = temp.x;
            int y = temp.y;

            if(find(x)==find(y)) continue;

            union(x, y);

            cnt++;
            ans += temp.cnt;
        }

        if(cnt==island-1)
            System.out.println(ans);

        else
            System.out.println(-1);
    }

    public static void bfs(Pair start, int island) {        //각 섬간의 거리를 측정
        Queue<Pair> queue = new LinkedList<>();
        boolean[][] v = new boolean[N][M];      //좌표 방문 체크 배열
        boolean[] v2 = new boolean[island+1];   //섬 방문 체크 배열
        queue.add(new Pair(start.x, start.y, start.label, -1, 0));
        v[start.x][start.y] = true;

        while(!queue.isEmpty()) {
            Pair temp = queue.poll();

            if(temp.idx==-1) {          //방향 설정이 안된 초기 좌표
                for(int i=0; i<4; i++) {
                    int nx = temp.x+dx[i];
                    int ny = temp.y+dy[i];

                    if(nx<0 || nx>=N || ny<0 || ny>=M || v[nx][ny]) continue;

                    v[nx][ny] = true;

                    if(map[nx][ny]==0) {
                        queue.add(new Pair(nx, ny, temp.label, i, temp.cnt+1));
                    }

                    else {
                        if(map[nx][ny]!=temp.label && !v2[map[nx][ny]] && temp.cnt>=2) {
                            v2[map[nx][ny]] = true;
                            pq.add(new Pair(temp.label, map[nx][ny], 0, 0, temp.cnt));
                        }
                    }
                }
            }

            else {          //방향이 정해진 좌표
                int nx = temp.x+dx[temp.idx];
                int ny = temp.y+dy[temp.idx];

                if(nx<0 || nx>=N || ny<0 || ny>=M || v[nx][ny]) continue;

                v[nx][ny] = true;

                if(map[nx][ny]==0) {        //바다면 계속 진행
                    queue.add(new Pair(nx, ny, temp.label, temp.idx, temp.cnt+1));
                }

                else {
                    if(map[nx][ny]!=temp.label && !v2[map[nx][ny]] && temp.cnt>=2) {    //바다가 아닌 경우 출발 섬과 번호가 다르고 방문하지 않은 섬이고 거리가 2인 경우
                        v2[map[nx][ny]] = true;
                        pq.add(new Pair(temp.label, map[nx][ny], 0, 0, temp.cnt));
                    }
                }
            }
        }
    }

    public static void dfs(int x, int y, int label) {          //섬 라벨링
        for(int i=0; i<4; i++) {
            int nx = x+dx[i];
            int ny = y+dy[i];

            if(nx<0 || nx>=N || ny<0 || ny>=M || visited[nx][ny] || map[nx][ny]!=1) continue;

            visited[nx][ny] = true;
            map[nx][ny] = label;
            queue.add(new Pair(nx, ny, label, -1, 0));  //섬인 경우 거리 측정을 위해 모두 큐에 넣음
            dfs(nx, ny, label);
        }
    }

    public static void union(int a, int b) {
        a = find(a);
        b = find(b);

        if(a<b)
            parent[b] = a;

        else
            parent[a] = b;
    }

    public static int find(int a) {
        if(parent[a]==a)
            return a;

        return parent[a] = find(parent[a]);
    }

    public static class Pair implements Comparable<Pair>{
        int x;              //행
        int y;              //열
        int label;          //섬 번호
        int idx;            //진행 방향
        int cnt;            //거리

        public Pair(int x, int y, int label, int idx, int cnt) {
            this.x = x;
            this.y = y;
            this.label = label;
            this.idx = idx;
            this.cnt = cnt;
        }

        public int compareTo(Pair p) {
            return this.cnt > p.cnt ? 1 : -1;
        }
    }
}
Blog Logo

pss407


Published

Image

pss's GitHub 블로그

Welcome my blog!

Back to Overview