并查集


适用场景

  • 将两个集合合并
  • 询问两个元素是否在一个集合当中

基本思路

  • 每个集合用一颗树来表示。树根的编号就是整个集合的编号。
  • 每个节点存储它的父节点,p[x]表示x的父节点
1
2
3
4
5
1.如何判断树根:if(p[x] == x)
2.如何求x的集合编号:while(p[x] != x) x = p[x]
3.如何合并两个集合:px是x的集合编号,py是y的集合编号。令p[x] = y(令x的父节点为y)

优化:路径压缩

例题1:合并集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。

现在要进行 m 个操作,操作共有两种:

M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。

每个结果占一行。

数据范围
1≤n,m≤105
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int p[N];//存储每个元素的父节点是谁

int find(int x){//返回x的祖宗节点 + 路径压缩
if(p[x] != x) p[x] = find(p[x]);//路径压缩,将寻找x祖宗路径上的所有点都指向祖宗节点
return p[x];
}

int main(){
ios::sync_with_stdio(false);
cin.tie(0);

cin>>n>>m;
for(int i = 1; i <= n; i++) p[i] = i;

while(m--){

char op[2];
int a, b;
cin>>op>>a>>b;
if(op[0] == 'M') p[find(a)] = find(b);
else {
if(find(a) == find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}

例题2:连通块中点的数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
Q2 a,询问点 a 所在连通块中点的数量;
输入格式
第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a b,Q1 a b 或 Q2 a 中的一种。

输出格式
对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No。

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围
1≤n,m≤105
输入样例:
5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5
输出样例:
Yes
2
3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//把连通块看成集合,两个连通块合并就相当于集合合并,因此可以用并查集算法
#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
int p[N], cnt[N];

int find(int x){
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}

int main (){
ios::sync_with_stdio(false);
cin.tie(0);

cin>>n>>m;

for(int i = 1; i <= n; i++){//初始化
p[i] = i;
cnt[i] = 1;
}

while(m--){
char op[3];
int a, b;

cin>>op;
if(op[0] == 'C'){
cin>>a>>b;
if(find(a) == find(b)) continue;//特判的原因是cnt += 此时会重复计数
cnt[find(b)] += cnt[find(a)];
p[find(a)] = find(b);
}
else if(op[1] == '1'){
cin>>a>>b;
if(find(a) == find(b)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
else{
cin>>a;
cout<<cnt[find(a)]<<endl;//返回a点祖宗节点的计数
}
}
return 0;
}