经典并查集(DSU) 可以将并查集想象成一个帮派合并的过程。
假设现在有n个人,一开始大家都不认识,每个人都可以是自己的老大。当然,有了ai,以后每个人都可以有自己的个体公司。当然,随着剧情的发展,会产生两种操作:合并、查询。
合并 :两个帮派结盟了,此时就需要一个帮派的老大带着全体小弟去归顺另一个老大。
查询 :随便抓两个人,问问他们的帮派的老大是不是一个人(即是不是一个帮派的)。
为了处理这两种操作,聪明的人类发明了并查集。
而如何实现并查集呢?我们可以使用数组,数组名可以使用par,而关于par数组里面存的是什么,后面我们将探讨当然不少题解会写far(farther的缩写)
一、初始化 一开始,每个人自成一派,也就是par[i] = i,这里的par数组存的是每个人的“直系上级”。当一个人的直系上级是他自己时,他就是这个帮派的首领(即树的根节点)。
1 2 3 4 5 6 7 8 9 vector<int > par; void Init (int n) { par.resize (n); for (int i = 0 ;i < n;++i){ par[i] = i; } }
或者可以用c++的内置函数iota优雅地进行初始化
二、查询 由于par数组只能看出一个人的直系上级,而为了找到一个人真正的老大,即帮派首领,我们需要顺着直系上级往上找,知道找到Par[i] == i,这个过程,需要用到递归。
如果par[x] == x,说明已经找到头了,否则,我们就继续递归,找这个人上级的上级。
1 2 3 4 5 int Find (int x) { if (par[x] == x) return x; return Find (par[x]); }
蛋蛋蛋蛋但是,最坏情况下,这棵树会退化成一条链,且不说爆栈一事,查询效率O(n),对于高级的数据结构来说,未免也太慢了。
那该怎么优化呢?我们可以在查询的时候,顺手把这条路径上所有人的直系上级直接改成首领。
于是我们可以对它进行路径压缩优化(Path Compression)
也就是这里的par[x] = Find(par[x]),要注意下这里是=而不是==。
1 2 3 4 5 6 7 int Find (int x) { if (par[x] == x){ return x; } return par[x] = Find (par[x]); }
1 2 3 4 5 6 int Find (int x) { if (par[x] != x){ par[x] = Find (par[x]); } return par[x]; }
当然两种写法都可,看自己喜欢哪一个。
三、合并 在合并两个帮派的时候,我们只需找到各自的老大,让其中一个老大认另一个老大做上级即可
1 2 3 4 5 6 7 void Union (int x,int y) { int fx = Find (x); int fy = Find (y); if (fx != fy){ par[fx] = fy; } }
但是这样子又会有问题,如果我把几万人的大帮派合并给只有一个人的小帮派时,会使得树的结构头重脚轻,增加后续的查询负担。骗你的,那几万人也不会同意。于是,我们又引入了按秩合并(Union by Rank & Size)
1 2 3 4 5 6 7 8 9 10 11 12 13 vector<int > rak; void Union (int x,int y) { int fx = Find (x),fy = Find (y); if (fx == fy) return ; if (rak[fx] < rak[fy]){ par[fx] = fy; rak[fy] += rak[fx]; }else { par[fy] = fx; rak[fx] += rak[fy]; } }
1 2 3 4 5 6 7 8 9 10 11 12 vector<int > siz; void Union (int x,int y) { int fx = Find (x),fy = Find (y); if (fx == fy) return ; if (siz[fx] < siz[fy]){ par[fx] = fy; siz[fy] += siz[fx]; }else { par[fy] = fx; siz[fx] += siz[fy]; } }
四、其他函数 Same函数 1 2 3 bool Same (int x,int y) { return Find (x) == Find (y); }
Getsize函数 1 2 3 int GetSize (int x) { return siz[Find (x)]; }
其他的函数就自己实现了
五、综合代码 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 #include <bits/stdc++.h> #define code using #define by namespace #define Mxiaocao std #define int long long const int N = 1e6 +5 ;code by Mxiaocao; vector<int > par,siz; int comp; void Init (int n) { par.resize (n); siz.resize (n,1 ); comp = n; iota (par.begin (),par.end (),0 ); } int Find (int x) { if (par[x] == x) return par[x]; return par[x] = Find (par[x]); } void Union (int x,int y) { x = Find (x),y = Find (y); if (x == y) return ; if (siz[x] < siz[y]) swap (x,y); par[y] = x; siz[x] += siz[y]; comp--; } bool Same (int x,int y) { return Find (x) == Find (y); } int GetSize (int x) { return siz[Find (x)]; } void solve () { int n,m; cin >> n >> m; Init (n); for (int i = 0 ;i < m;++i){ int z,x,y; cin >> z >> x >> y; x--,y--; if (z == 1 ){ Union (x,y); }else if (z == 2 ){ if (Find (x) == Find (y)){ cout << "Y\n" ; }else { cout << "N\n" ; } } } return ; } signed main () { ios::sync_with_stdio (false ); cin.tie (nullptr ); int t = 1 ; while (t--){ solve (); } return 0 ; }
相关文章
[[5-3-2-MST]] [[5-2-8-Tarjan-Algorithm]]