https://www.acmicpc.net/problem/2606
유니온 파인드(Union Find)를 이용하면, 원소가 어떤 집합에 포함되어 있는지 알 수 있다.
유니온 파인드에서는 2가지 연산이 필요하다.
find(x) : x가 포함된 집합을 찾는다. (root 원소를 찾는다.)
makeUnion(x, y) : x와 y가 같은 집합에 포함되도록 합친다. parent[y] = x가 된다.
그리고 각 원소별로 자신의 부모를 저장할 parent 배열이 필요하다.
바이러스 문제는 그래프(링크드 리스트)를 만드는 기본 문제지만, 집합으로도 해결 가능하다.
이 문제에서는 1번 컴퓨터와 연결된 컴퓨터의 수를 찾아야 한다.
Find( 2 ~ N ) 과 Find( 1 )의 집합이 같으면 1번 컴퓨터와 연결되었다고 볼 수 있다.
먼저 예제에 대해 집합을 만들어보자. (1 2 / 2 3 / 1 5 / 5 2 / 5 6 / 4 7)
parent 배열은 i의 부모를 가르킨다. 처음에는 자기 자신을 가르키도록 한다.
int parent[MAX]; /* parent[i] : i의 parent */
입력 1 2 → makeUnion(1, 2), parent[2] = 1이 된다.
입력 2 3 → makeUnion(2, 3), parent[3] = 2이 된다.
parent[3]은 2이고, parent[2]는 1이기 때문에 결국 find(3)과 find(1)의 결과는 같다. (같은 집합이다.)
따라서 parent[3] = 1로 표시할 수 있다. 이 과정을 경로 압축이라고 한다.
입력 1 5 → makeUnion(1, 5), parent[5] = 1이 된다.
입력 5 2 → makeUnion(5, 2), parent[2] = 1, parent[5] = 1이므로 변화가 없다.
입력 5 6 → makeUnion(5, 6), parent[6] = 5, parent[5] = 1이므로 parent[6] = 1이 된다.
입력 4 7 → makeUnion(4, 7), parent[7] = 4가 된다.
parent[1, 2, 3, 5, 6] = 1이 되고, parent[4, 7] = 4가 되어 1과 연결된 컴퓨터는 총 4개가 된다.
구현
원소 1과 2가 같은 집합인지 확인하려면, find를 이용하여 1과 2의 root 부모의 원소가 같은지 확인하면 된다.
(find(1) == find(2))
find는 재귀 호출을 이용하여 아래와 같이 구현한다.
x == parent[x]면 자기 자신을 return하고, 그렇지 않으면 부모를 return한다.
재귀 호출이므로 x == parent[x]인 경우까지 find가 계속 호출 되어 root 원소를 return한다.
int find(int x)
{
if (x == parent[x]) return x;
return find(parent[x]);
}
만약 parent[N]의 부모가 N - 1, parent[N - 1] = N - 2, ... 최종적으로 parent[2] = 1이라고 하자.
N과 2가 같은 집합인지 확인하기 위해 N의 최종 부모를 찾기까지 O(N)이 걸린다.
따라서 find 함수에서 경로 압축을 적용하여 parent[N]이 최종적으로 1이 되도록 만든다.
int find(int x)
{
if (x == parent[x]) return x;
return parent[x] = find(parent[x]); // 경로 압축
}
즉, 실제 원소의 부모가 중요한 것이 아니라, 최종 root가 무엇인지가 중요하다.
find 함수를 만들었으니, makeUnion은 아래와 같이 만들 수 있다.
x와 y의 각 root 원소를 찾아서, parent 배열로 연결해준다.
/* y의 parent를 x로 설정 */
void makeUnion(int x, int y)
{
x = find(x);
y = find(y);
if (x != y) parent[y] = x;
}
최종 코드는 아래와 같다.
#include <stdio.h>
#define MAX (100 + 10)
int N, M;
int parent[MAX]; /* parent[i] : i의 parent */
int find(int x)
{
if (x == parent[x]) return x;
return parent[x] = find(parent[x]); // 경로 압축
}
void makeUnion(int x, int y)
{
x = find(x);
y = find(y);
if (x != y) parent[y] = x;
}
int main(void)
{
int ans;
scanf("%d %d", &N, &M);
for (int i = 1; i <= N; i++) parent[i] = i;
for (int i = 0; i < M; i++)
{
int x, y;
scanf("%d %d", &x, &y);
makeUnion(x, y);
}
ans = 0;
for (int i = 2; i <= N; i++)
if (find(i) == find(1)) ans++;
printf("%d\n", ans);
return 0;
}
'알고리즘 > BAEKJOON' 카테고리의 다른 글
BOJ 5465 : 곰돌이 (0) | 2021.06.12 |
---|---|
BOJ 1717 : 집합의 표현 (0) | 2021.06.10 |
BOJ 2805 : 나무 자르기 (0) | 2021.06.05 |
BOJ 14442 : 벽 부수고 이동하기 2 (0) | 2021.06.02 |
BOJ 2206 : 벽 부수고 이동하기 (0) | 2021.05.30 |
댓글