一个代替tarjan的缩点算法:并查集维护缩点

不知道哪里来的野鸡科技,之前从来没有遇到过,因为在学边分治缩点的时候题解里提到了这个东西,我以为要用,结果不是同一回事。

但是还是记录一下,思想还是有点用处的。


我们用一个并查集表示一个强连通分量,原先缩点后的 belong[u] 可以改成 find(u),也就是以并查集的根为强连通分量的代表结点。

用并查集维护缩点的思想和代码都很简单,就是在 DFS 时,每次找到一条 DFS 树上的返祖边,就把这个祖先和我的并查集进行合并。在 DFS 的过程中需要用一个 dep 数组记录深度,每个强连通分量的代表结点就是深度最小的那个结点,这个很重要,下面会讲到。

具体的,DFS 时从根到当前结点的路径上的点 dep 值为正且递增,其他遍历过的点 dep 设为-1,未遍历的点 dep 为初值0。当下一个结点 dep 为正,说明找到了一条返祖边,就将这两个点进行合并。在沿树边回溯时,我们会把这个连通块上的点依次合并,判断方法就是查询儿子点所在连通块代表结点的深度,如果深度大于父亲,说明这个连通块的最顶端结点比父亲还要高,那么就将树边上这对父子结点合并。

刚刚有提到用深度最小的结点作为并查集的根,如果我们并查集的合并顺序随意,这个连通块的最顶部结点的深度信息就被破坏了,可能会出现连通块的代表结点已经被遍历过,dep 值设为了-1,但是另一个树上分支有同属于这个连通块的点,就会因为判断错误而不去合并。

代码很简单,需要维护的连通块信息,比如点数,权值和等,可以在 merge 函数里更新。

#include <bits/stdc++.h> #define ll long long using namespace std; const int N=501010; const int inf=0x3f3f3f3f; inline int read() { int sum = 0, ff = 1; char c = getchar(); while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); } while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); } return sum * ff; } int n,m; int fa[N],dep[N]; vector <int> e[N]; int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); } void merge(int x,int y) { if(x==y) return ; if(dep[x]<dep[y]) swap(x,y); fa[x] = y; } void tarjan(int u) { for(int v : e[u]) { if(!dep[v]) { dep[v] = dep[u] + 1; tarjan(v); } int vv = find(v), uu = find(u); if(dep[vv] > 0) merge(uu,vv); } dep[u] = -1; } int main() { int x,y; n = read(); m = read(); for(int i=1;i<=n;i++) fa[i] = i; for(int i=1;i<=m;i++) { x = read(); y = read(); e[x].push_back(y); } for(int i=1;i<=n;i++) { if(!dep[i]) tarjan(i); } for(int i=1;i<=n;i++) cout<<"belong["<<i<<"] = "<<find(i)<<"\n"; return 0; } /* 5 5 1 2 2 3 3 1 3 4 5 2 */

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/18341801.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示