2월 29, 2024

[백준] 16947번 서울지하철문제 그래프를 활용하여 풀기

1. 문제

1) 링크

www.acmicpc.net/problem/16947

2) 문제

지하철 2호선에는 51개의 역이 있고, 역과 역 사이를 연결하는 구간이 51개 있다. 즉, 정점이 51개이고, 양방향 간선이 51개인 그래프로 나타낼 수 있다. 2호선은 순환선 1개와 2개의 지선으로 이루어져 있다. 한 역에서 출발해서 계속 가면 다시 출발한 역으로 돌아올 수 있는 노선을 순환선이라고 한다. 지선은 순환선에 속하는 한 역에서 시작하는 트리 형태의 노선이다.

두 역(정점) 사이의 거리는 지나야 하는 구간(간선)의 개수이다. 역 A와 순환선 사이의 거리는 A와 순환선에 속하는 역 사이의 거리 중 최솟값이다.

지하철 2호선과 같은 형태의 노선도가 주어졌을 때, 각 역과 순환선 사이의 거리를 구해보자.

3) 입력

첫째 줄에 역의 개수 N(3 ≤ N ≤ 3,000)이 주어진다. 둘째 줄부터 N개의 줄에는 역과 역을 연결하는 구간의 정보가 주어진다. 같은 구간이 여러 번 주어지는 경우는 없고, 역은 1번부터 N번까지 번호가 매겨져 있다. 임의의 두 역 사이에 경로가 항상 존재하는 노선만 입력으로 주어진다.

4) 출력

총 N개의 정수를 출력한다. 1번 역과 순환선 사이의 거리, 2번 역과 순환선 사이의 거리, ..., N번 역과 순환선 사이의 거리를 공백으로 구분해 출력한다.

 

더 자세한 문제의 제한사항은 위의 링크에 들어가서 확인해보자.


2. 풀이


이 문제는 싸이클을 찾는

 

https://www.programmingstory.com/2024/02/16929-two-dots-dfs.html


위의 포스팅에서 다룬 내용과 비슷하지만 다른 점이 있다. 



만약 위와 같은 그래프가 있다면1-2-3-4-5-6 이런식으로 돈다고 생각했을 때 싸이클이 1개 만들어지지만 5번과 6번 노드는 싸이클의 일부가 아니다. 따라서 지하철 문제는 전체를 돈 후 싸이클이 완성되더라도 해당 노드가 싸이클의 일부인지 아닌지를 판단하는 과정이 하나 더 필요하다. 

 


먼저 이 문제의 find함수의 원형을 살펴보자

find(int prev, int x)라는 함수는 싸이클 찾는 방법을 활용하기 위해 전의 노드와 현재 노드의 값을 매개변수로 받는 함수이다. 이 문제의 return 값을 정해보았다. (이 숫자를 무엇으로 정하느냐는 본인의 마음이다)

  • -2: 싸이클은 찾았으나, 해당 노드가 싸이클에 포함되지 않음. 위의 그림에서 5번과 6번 같은 느낌
  • -1: 싸이클을 찾지 못했다. (전체를 다 돈 후에도 return이 되어 있지 않으면 return -1을 해주는 것)
  • 0~n-1: 싸이클을 찾았다. 이 경우, 싸이클의 맨 처음 index를 return해준다. (처음 인덱스라는 것은 예를 들어 1 노드에서 시작하여 다시 1로 돌아왔을 경우 시작 인덱스가 1이 된다.)

find 함수의 코드는 아래와 같다. 

 public static int find(int prev, int x){
        if (check[x]==1){   //예전에 방문한 노드에 다시 방문 (싸이클을 찾음)
            return x;
        }
        check[x]=1;
        for(int y: a[x]){ //인접리스트 사용
            if (y==prev){ //이전 노드와 같으면 continue
                continue;
            }
            int num=find(x, y);
            if (num==-2) return -2;  //싸이클을 찾지 못함
            if (num>=0){
                check[x]=2; //싸이클의 일부라는 뜻
                if (x==num){return -2;}  //시작노드와 같으면 이 이후로는 싸이클이 아니므로 -2 리턴
                else return num;
            }
        }
        return -1; 
    }

 

여기서 check 배열이 나오는데 check 배열에는 그냥 방문한 노드면 1을 저장, 방문했는데 싸이클 (순환선)의 일부이면 2를 저장하는 식으로 구했다. 


그러면 이제 각 노드에서 순환선 사이의 거리를 구해야 한다. 해당 노드가 싸이클의 일부면 check에다가 2를 저장했으므로, check의 값이 2 이면 거리는 0이다. 

맨 처음 queue에는 순환선인 node들만 추가를 하고 이후,

    while (!q.isEmpty()) {
            int x = q.remove();
            for (int y : a[x]) {
                if (distant[y] == -1) {
                    q.add(y);
                    distant[y] = distant[x]+1;
                }
            }
        }

위와 같이 코드를 구성해주면 된다. 인접한 것은 그 전 노드의 거리에다가 1을 더한 것이기 때문이다. 


3. 코드

위 과정을 거쳐 전체 코드는

import java.util.*;

public class Main{
    public static ArrayList<Integer>[] a;
    public static int[] check;
    public static int[] distant;
    public static int find(int prev, int x){
        if (check[x]==1){
            return x;
        }
        check[x]=1;
        for(int y: a[x]){
            if (y==prev){
                continue;
            }
            int num=find(x, y);
            if (num==-2) return -2;
            if (num>=0){
                check[x]=2;
                if (x==num){return -2;}
                else return num;
            }
        }
        return -1; 
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        
        a=(ArrayList<Integer>[])new ArrayList[n];
        check=new int[n];
        distant=new int[n];
        for(int i=0; i<n; i++){
            a[i]=new ArrayList<Integer>();
        }
         Queue<Integer> q = new LinkedList<>();
        for(int i=0; i<n; i++){
            int from=sc.nextInt()-1;
            int to=sc.nextInt()-1;
            a[from].add(to);
            a[to].add(from);
        }
        find(-1, 0);
        
        for (int i=0; i<n; i++) {
            if (check[i] == 2) {
                distant[i] = 0;
                q.add(i);
            } else {
                distant[i] = -1;
            }
        }
        while (!q.isEmpty()) {
            int x = q.remove();
            for (int y : a[x]) {
                if (distant[y] == -1) {
                    q.add(y);
                    distant[y] = distant[x]+1;
                }
            }
        }
        for (int i=0; i<n; i++) {
            System.out.print(distant[i] + " ");
        }
        System.out.println();
    }
}

위와 같이 작성할 수 있다.