알고리즘

[백준 P1717] 집합의 표현 Java

KYBee 2022. 9. 19. 22:41

P1717 집합의 표현

 

1717번: 집합의 표현

첫째 줄에 n(1 ≤ n ≤ 1,000,000), m(1 ≤ m ≤ 100,000)이 주어진다. m은 입력으로 주어지는 연산의 개수이다. 다음 m개의 줄에는 각각의 연산이 주어진다. 합집합은 0 a b의 형태로 입력이 주어진다. 이는

www.acmicpc.net

문제

초기에 {0}, {1}, {2}, ... {n} 이 각각 n+1개의 집합을 이루고 있다. 여기에 합집합 연산과, 두 원소가 같은 집합에 포함되어 있는지를 확인하는 연산을 수행하려고 한다.

집합을 표현하는 프로그램을 작성하시오.

입력값

첫째 줄에 n(1 ≤ n ≤ 1,000,000), m(1 ≤ m ≤ 100,000)이 주어진다. m은 입력으로 주어지는 연산의 개수이다. 다음 m개의 줄에는 각각의 연산이 주어진다. 합집합은 0 a b의 형태로 입력이 주어진다. 이는 a가 포함되어 있는 집합과, b가 포함되어 있는 집합을 합친다는 의미이다. 두 원소가 같은 집합에 포함되어 있는지를 확인하는 연산은 1 a b의 형태로 입력이 주어진다. 이는 a와 b가 같은 집합에 포함되어 있는지를 확인하는 연산이다. a와 b는 n 이하의 자연수 또는 0이며 같을 수도 있다.

7 8
0 1 3
1 1 7
0 7 6
1 7 1
0 3 7
0 4 2
0 1 1
1 1 1

출력값

1로 시작하는 입력에 대해서 한 줄에 하나씩 YES/NO로 결과를 출력한다. (yes/no 를 출력해도 된다)

NO
NO
YES

알고리즘

{0}, ….. {n} 까지 n + 1개의 집합이 존재한다. 그리고 두 개의 집합을 합하는 연산과 두 원소가 같은 집합에 포함되어 있는지를 확인하는 연산을 구현해야 한다.

전형적인 Union-Find 문제이다. Union으로 두 개의 집합을 합하는 연산을 구현하고 Find로 두 원소가 같은 집합에 포함되어 있는지를 확인하면 된다.

우선 n + 1개의 원소가 몇 번째 집합에 속해있는지 저장할 배열을 선언한다. 배열은 parent로 이름을 정했다.

parent = new int[N + 1];

for (int i = 1; i <= N; i++) {
    parent[i] = i;
}

find 연산을 구현한다. find는 매개변수로 받아오는 원소가 속한 집합의 대표값을 반환한다. 여기서 대표값은 지정하기 나름이지만 이 문제에서는 집합 내에서 가장 작은 값이 해당 집합의 대표값이 된다고 하겠다. 그래서 매개변수로 받아온 원소와 그 원소의 parent 값이 다르다면 그 원소는 해당 집합의 대표값이 아니므로 대표값을 찾기 위해 한번 더 find를 호출하게 된다. 이를 구현한 코드는 다음과 같다.

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

그 다음 합집합 연산을 구현한다. 합집합 연산은 매개변수로 받은 두 원소의 대표값을 비교하여 값이 서로 다르다면 이 둘을 연결해주는 역할을 한다. 두 원소의 대표값이 다르다는 것은 이 둘이 속한 집합이 다르다는 것을 의미한다. 따라서 find 값으로 대표값을 찾고 두 값이 다르다면 더 작은 대표값을 가지는 집합에 큰 대표값을 가지는 집합을 합쳐주면 된다. 여기서 더 작은 대표값으로 바꿔주는 이유는, 현재 대표값을 집합 내에서 가장 작은 값으로 정했기 때문이다. 결과적으로 대표값이 큰 집합의 값을 바꾸어주면 된다. 

설명이 애매할 수 있지만 코드를 보면 꽤 직관적이다. a 원소가 속한 집합의 대표값은 aParent, b는 bParent로 표현한다. 둘 중에서 더 큰 값의 parent 값을 더 작은 값으로 수정해준다. 이제는 a, b를 find하면 두 값이 같은 값을 가지게 되고 이를 두 원소가 연결되어 있다고 판단하면 된다. 

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

    if (parentA < parentB) {
        parent[parentA] = parentB;
    } else if (parentA > parentB) {
        parent[parentB] = parentA;
    }
}

union과 find 연산을 구현했다면 이제 문제 조건에 맞게 코드를 작성하면 된다.

코드

import java.util.*;
import java.io.*;

public class Main {
    static int N, M;
    static int[] parent;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        parent = new int[N + 1];

        for (int i = 1; i <= N; i++) {
            parent[i] = i;
        }

        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int op = Integer.parseInt(st.nextToken());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            if (op == 0) {
                union(a, b);
            } else {
                if (find(a) == find(b)) {
                    System.out.println("YES");
                } else {
                    System.out.println("NO");
                }
            }
        }
    }

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

        if (parentA < parentB) {
            parent[parentA] = parentB;
        } else if (parentA > parentB) {
            parent[parentB] = parentA;
        }
    }

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