并查集
并查集(Union-find Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
并查集的思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,我们只要找到了某个元素的的树根,就能确定它在哪个集合里。
yxc模板
链接:https://www.acwing.com/blog/content/404/
并查集 —— 模板题:AcWing 836. 合并集合, AcWing 837. 连通块中点的数量
(1)朴素并查集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int p[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
for (int i = 1; i <= n; i ++ ) p[i] = i;
p[find(a)] = find(b);
|
(2)维护size的并查集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| int p[N], size[N];
int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x]; }
for (int i = 1; i <= n; i ++ ) { p[i] = i; size[i] = 1; }
a = find(a),b = find(b); if (find(a) != find(b)){ p[a] = b; Size[b] += Size[a]; }
|
(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
| int p[N], d[N];
int find(int x) { if (p[x] != x) { int u = find(p[x]); d[x] += d[p[x]]; p[x] = u; } return p[x]; }
for (int i = 1; i <= n; i ++ ) { p[i] = i; d[i] = 0; }
p[find(a)] = find(b); d[find(a)] = distance;
|
原理介绍
参考1:算法训练营进阶篇。
参考2:秦淮岸dalao的讲义,https://www.acwing.com/blog/content/197/,举的例子非常棒!
参考3:https://zhuanlan.zhihu.com/p/93647900
参考4:https://oi-wiki.org/ds/dsu/
假设存在一个庞大的部落,人数众多,我们需要判断给出的两人是否具有亲戚关系。
现给定亲戚关系的两条重要性质:
- 传递性:若x、y是亲戚,y、z是亲戚,那么x、z是亲戚;
- 集合合并(连通集合):若x、y是亲戚,则x的亲戚也是y的亲戚,y的亲戚也是x的亲戚。
我们可以用并查集来处理具有这两种性质的亲戚关系的判定问题。
算法步骤:1.初始化;2.查找;3.合并。
y总的模板真的很不错,hh。
还是直接上例题。
初始化时并查集编号从1开始!
洛谷P3367并查集(模板题)
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
| 题目描述 如题,现在有一个并查集,你需要完成合并和查询操作。
输入格式 第一行包含两个整数 N,M,表示共有 N 个元素和 M 个操作。
接下来 M 行,每行包含三个整数 Z,X,Y 当 Z=1 时,将 X与Y所在的集合合并。 当 Z=2 时,输出 X与Y是否在同一集合内,是的输出 Y ;否则输出 N 。
输出格式 对于每一个 Z=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N 。
输入输出样例 输入 4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4 输出 N Y N Y
对于 100% 的数据,1≤N≤10^4 ,1≤M≤2×10^5
|
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
| #include <cstdio> #include <algorithm> using namespace std; const int N = 100010;
int p[N]; int n,m;
int find(int a){ if (p[a] != a) p[a] = find(p[a]); return p[a]; }
int main(){ scanf("%d%d",&n,&m); for (int i = 1;i <= n;i++) p[i] = i;
int z,x,y;char res; while (m--){ scanf("%d%d%d",&z,&x,&y); if (z == 1) p[find(x)] = find(y); else{ res = find(x) == find(y) ? 'Y' : 'N'; printf("%c\n",res); } } return 0; }
|
初始化:刚开始的时候每个点就是一个独立的集合,且这个集合树的树根就是本身。
查找:路径压缩,每次执行find操作的时候,把访问过的节点(也就是所有元素的父亲),都统统指向树根祖宗.这种方法可以避免出题人刻意卡掉链式结构。
N次合并M次查找的时间复杂度为:O(M*Alpha(N)),Alpha是Ackerman函数的某个反函数,在很大范围内它的值不超过4,接近线性,所以可以近似看成是:O(1)。
最坏情况时间复杂度为:O(m*logn)。这个记住就行。