一. 模板
(1)朴素并查集:
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
(2)维护size的并查集(用cnt代替size,size会报错):
int p[N], size[N];
//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1; // 初始化size
}
// 合并a和b所在的两个集合:
size[find(b)] += size[find(a)]; // 先更新size,再合并
p[find(a)] = find(b);
(3)维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
二. 总结
- 合并时必须让x,y的祖宗相等,如果仅仅等于父亲或者本身的话,树的高度太高了,会超内存
- 原理图:
- 路径压缩
三. 例题
AC代码:
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
int p[N]; // 父节点
// 返回x的祖宗结点 + 路径压缩
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);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; ++i) p[i] = i; // 祖宗结点的标志:祖宗结点的父节点等于其本身
char op;
int x, y;
while (m--) {
cin >> op >> x >> y;
if (op == 'M') p[find(x)] = find(y); // 合并:让x的祖宗的父节点变为y的祖宗
else {
if (find(x) == find(y)) cout << "Yes" << endl; // 比较祖宗结点
else cout << "No" << endl;
}
}
}