双连通分量 点双和边双

算法:双连通分量

1.定义

1.双连通分量的定义如下:

在一张连通的无向图中,对于两个点 u 和 v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说 u 和 v 边双连通。

在一张连通的无向图中,对于两个点 u 和 v,如果无论删去哪个点(只能删去一个,且不能删 u 和 v 自己)都不能使它们不连通,我们就说 u 和 v 点双连通。

2.关于传递性

边双连通具有传递性,即,若 x,y 边双连通,y,z 边双连通,则 x,z 边双连通。

点双连通 具有传递性,反例如下图,A,B 点双连通,B,C 点双连通,而 A,C 点双连通。

对于一个无向图中的 极大 边双连通的子图,我们称这个子图为一个 边双连通分量

对于一个无向图中的 极大 点双连通的子图,我们称这个子图为一个 点双连通分量

2.求解

1.边双连通分量

若 A,B 边双连通,必有两条不经过相同边的 A→B 的路径。

当然,这是一个无向图,所以可以把其中一条路径转化为 B→A 的路径,于是我们就有了一个环。

于是,我们推出了一个重要的结论:A,B 边双连通 ⟺A,B 在一个环内。

于是,我们得出,一个环一定是一个边双连通的子图,边双连通分量则是几个环,每个环都与其它环中的至少一个有公共点。这不和 SCC 差不多吗……

好了,粘板子(模板题在这里):

#include<bits/stdc++.h>
using namespace std;
int n, m, dfn[514514], low[514514], instk[514514];
int dcnt, anscnt, stk[514514], top;
vector<int> e[514514], ans[514514];
void tarjan(int u, int fa){
    bool check = 0;
    dfn[u] = low[u] = ++dcnt;
    stk[++top] = u, instk[u] = 1;
    for(auto v: e[u]) if(v == fa && !check) check = 1; //防止直接回到父亲,check是判重边
    else if(!dfn[v]) tarjan(v, u), low[u] = min(low[u], low[v]); //如果 v 没访问过,递归下去
    else if(instk[v]) low[u] = min(low[u], low[v]); //如果 v 在栈中,更新 low[u]
    if(dfn[u] == low[u]){ // 如果没有更新 low[u],则 u 是边双连通分量中 dfn 最小的点,更新 ans
        ++anscnt;
        do{
            ans[anscnt].push_back(stk[top]);
            instk[stk[top]] = 0;
        }while(stk[top--] != u);
    }
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v),
        e[u].push_back(v), e[v].push_back(u);
    for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i, 0); //防止图不连通
    printf("%d\n", anscnt);
    for(int i = 1; i <= anscnt; i++){
        printf("%lld ", ans[i].size());
        for(int j = 0; j < ans[i].size(); j++) printf("%d%c", ans[i][j], " \n"[j == ans[i].size() - 1]);
    }
    return 0;
}

2.点双连通分量

同上面的分析,A,B 在一个环内时,A,B 点双连通。
但还有一个特例:A,B 之间有连边。 image

同时,点双连通没有传递性,所以点双连通分量就是一个极大的环或者两个不在同一个环内且有边连接的点。

好了,粘板子(模板题在这里):

#include<bits/stdc++.h>
using namespace std;
int n, m, dfn[514514], low[514514];
int anscnt, stk[514514], top, cnt;
vector<int> ans[514514], e[514514];
void tarjan(int u, int fa){
    dfn[u] = low[u] = ++cnt, stk[++top] = u;
    if(fa == 0 && e[u].empty()) ans[++anscnt].push_back(u); //孤立点
    for(auto v: e[u]) if(v != fa)
        if(!dfn[v]){
            tarjan(v, u); low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]){//u是割点
                anscnt++;
                do ans[anscnt].push_back(stk[top--]);
                while(stk[top + 1] != v); //u可能属于多个点双,所以这里只弹栈到v
                ans[anscnt].push_back(u);
            }
        }
        else low[u] = min(low[u], dfn[v]); //是dfn[v],否则就变成几个环了
}
void nll(){ return; }
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, u, v; i <= m; i++) scanf("%d%d", &u, &v), (u != v)?(e[u].push_back(v), e[v].push_back(u)):nll();//记得判自环
    for(int i = 1; i <= n; i++) if(!dfn[i]) tarjan(i, 0);
    printf("%d\n", anscnt);
    for(int i = 1; i <= anscnt; i++){
        printf("%d ", ans[i].size());
        for(int j = 0; j < ans[i].size(); j++)
            printf("%d%c", ans[i][j], " \n"[j == ans[i].size() - 1]);
    }
    return 0;
}

posted @ 2025-05-20 10:28  katago  阅读(241)  评论(0)    收藏  举报