一个代替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

*/

posted @ 2024-08-04 15:40  maple276  阅读(20)  评论(0编辑  收藏  举报