并查集
例题
一共有 \(n\) 个数,编号是 \(1 \sim 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 \le n,m \le 10^5\)
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
No
Yes
什么是并查集
并查集常用于快速计算森林中有多少棵树。
并指合并操作,查指递归追查祖先,集指处理对象是集合。
如何实现并查集
初始化并查集
用一个一维数组 \(p\) 模拟并查集。开始时每个集合都是以自己为祖先
即:
for (int i = 1; i <= n; ++i)
p[i] = i; // n为结点的个数(即数的个数)
合并操作
我们需要先实现一个函数:查找当前节点的祖先节点,在寻找祖先节点的过程中,使用类似记忆化搜索的方法同步更新父节点,这就是一种叫做路径压缩优化的技巧,可以有效减少追查深度。
代码:
int find(int x) { // 查找x的祖先节点(路径压缩)
if (p[x] != x)
p[x] = find(p[x]); // 祖先节点的父节点不是自己时更新父节点
return p[x];
}
合并时,将两个集合的祖宗节点换掉就可以了
p[find(x)] = find(y);
查询操作
这个直接判断两个节点的祖宗节点是不是同一个节点就可以了,代码:
if (find(x) == find(y))
puts("Yes");
else
puts("No");
例题实现代码
#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N];
int find(int x) {
if (p[x] != x)
p[x] = find(p[x]);
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i)
p[i] = i;
while (m--) {
string op;
int x, y;
cin >> op >> x >> y;
x = find(x), y = find(y);
if (op == "M")
p[x] = y;
else {
if (x == y)
puts("Yes");
else
puts("No");
}
}
return 0;
}