zoukankan      html  css  js  c++  java
  • tarjan算法 双连通分量

    ##一、边双连通分量

    ##定义

    ##边双连通图:若一幅无向图中去掉任意一条边都不会改变此图的连通性,即不存在桥,则该图称为边双连通图。
    ##边双连通分量:无向图中,删除任意一条边仍连通的块,简称“e-DCC“。
    ##定理:一张图是边双连通,当且仅当任意一条边都包含在一个简单环中(即无桥)
    ##性质:桥把整张图拆成若干个“e-DCC”,桥不在任意“e-DCC”上。

    tarjan求边双连通分量

    ##算法:首先用tarjan标记出所有的桥,然后用dfs遍历整张图(遍历过程中不访问桥边),求出连通块数量,就是边双连通分量数量。
    ##代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=5e4+5,M=3e5+5;
    int n,m,x,y,cnt=1,hd[N],to[M<<1],nxt[M<<1],dfn[N],low[N],num,c[N],dcc;
    bool g[M<<1];
    void add(int x,int y){
        to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
    }
    void tarjan(int x,int fa){
        dfn[x]=low[x]=++num;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i];
            if(!dfn[y]){
                tarjan(y,i);
                low[x]=min(low[x],low[y]);
                if(low[y]>dfn[x]) g[i]=g[i^1]=1;
            }
            else if(i!=(fa^1)) low[x]=min(low[x],dfn[y]);
        }
    }
    void dfs(int x){
        c[x]=dcc;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i];
            if(c[y]||g[i]) continue;
            dfs(y);
        }
    }
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%lld%lld",&x,&y);
            add(x,y),add(y,x);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i]) tarjan(i,0);
        for(int i=1;i<=n;i++)
            if(!c[i]) ++dcc,dfs(i);
        printf("%lld
    ",dcc);
        return 0;
    }
    

    ##边双连通分量缩点:将所有的边双连通分量缩成一个点,把边(x,y)看作连接编号c[x]和c[y]的边双连通分量对应节点的无向边,原图会变成一棵树。
    ##代码:

    int cnt2=1,hd2[N],to2[N<<1],nxt2[N<<1]; 
    void add2(int x,int y){
        to2[++cnt2]=y,nxt2[cnt2]=hd2[x],hd2[x]=cnt2;
    }
    /*.......*/
    int main(){
        /*.......*/
        for(int i=2;i<=cnt;i++){
            int x=to[i^1],y=to[i];
            if(c[x]!=c[y]) add2(c[x],c[y]);
        }
        //dcc 为缩点后森林的点数,cnt2/2 为缩点后森林的边数 
        for(int i=2;i<cnt2;i+=2)
            printf("%lld %lld
    ",to2[i^1],to2[i]); 
    } 
    

    ##定理:1、当缩点后的图中,如果叶子数leaf(入度为1)为1,则将一个有桥图通过加边变成边双连通图至少要添加的边数为0;否则为 (leaf+1)/2。

    ##二、点双连通分量

    ##定义

    ##点双连通图:若一幅无向图中去掉任意一个点都不会改变此图的连通性,即不存在割点,则称作点双连通图。
    ##点双连通分量:无向图中,删除任意一个点仍连通的块,简称“v-DCC”。
    ##定理:一张无向图是点双连通分量的条件,满足以下两个里满足一个:
    1、图的顶点数不超过2
    2、图中任意两点都同时包含在至少一个简单环中。
    ##性质:1、“v-DCC”中没有割点。
    2、若两个“v-DCC”之间有公共点,那么这个点就是原图的割点。
    3、在无向图中,割点可能属于多个“v-DCC”,但其它点只能属于一个“v-DCC”。
    4、割点讲整张图分成若干个“v-DCC”。

    ##tarjan求点双连通分量

    ##算法:对于点双连通分量,实际上在求割点的过程中就能顺便求出每个点双连通分量。建立一个栈,存储当前双连通分量,在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到满足 dfn[x]≤low[y],说明 x 是一个割点,同时把边从栈顶一个个取出,直到遇到边 (x,y) 为止。取出的这些边与其相连的点,组成一个 v-DCC。对于两个 v-DCC,最多只有一个公共点即割点。
    ##代码:

    #include<bits/stdc++.h>
    #define int long long
    using namespace std;
    const int N=5e4+5,M=3e5+5;
    int n,m,x,y,cnt=1,hd[N],to[M<<1],nxt[M<<1],dfn[N],low[N],num,root,top,tot,st[N];
    bool g[N];
    vector<int>dcc[N];
    void add(int x,int y){
        to[++cnt]=y,nxt[cnt]=hd[x],hd[x]=cnt;
    }
    void tarjan(int x){
        dfn[x]=low[x]=++num,st[++top]=x;
        if(x==root&&!hd[x]) return (void)(dcc[++tot].push_back(x));    //孤立点 
        int flag=0;
        for(int i=hd[x];i;i=nxt[i]){
            int y=to[i];
            if(!dfn[y]){
                tarjan(y),low[x]=min(low[x],low[y]);
                if(low[y]>=dfn[x]){
                    flag++;
                    if(x!=root||flag>1) g[x]=1;
                    dcc[++tot].push_back(st[top]);
                    while(st[top]!=y) dcc[tot].push_back(st[--top]);
                    --top,dcc[tot].push_back(x);
                }
            }
            else low[x]=min(low[x],dfn[y]);
        }
    }
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int i=1;i<=m;i++){
            scanf("%lld%lld",&x,&y);
            if(x==y) continue;
            add(x,y),add(y,x);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i]) root=i,tarjan(i);
        for(int i=1;i<=tot;i++)
          for(int j=0;j<dcc[i].size();j++)
           printf("%lld%c",dcc[i][j],j==dcc[i].size()-1?'
    ':' ');
        return 0;
    }
    

    ##点双连通分量缩点:设图中有p个割点,t个“v-DCC” 。我们建立一个包含p+t个点的新图,割点和v-DCC作为新图的节点,并在每个割点与包含它的所有 v-DCC 之间两边。
    ##代码:

    int cnt2=1,hd2[N],to2[N<<1],nxt2[N<<1]; 
    void add2(int x,int y){
        to2[++cnt2]=y,nxt2[cnt2]=hd2[x],hd2[x]=cnt2;
    }
    /*.......*/
    int main(){
        /*.......*/
        //给每个割点一个新的编号(编号从 tot+1 开始)
        num=tot;
        for(int i=1;i<=n;i++)
            if(g[i]) k[i]=++num;
        //建新图,从每个 v-DCC 到它包含的所有割点连边
        for(int i=1;i<=tot;i++)
            for(int j=0;j<dcc[i].size();j++){
                int x=dcc[i][j];
                if(g[x]) add2(i,k[x]),add2(k[x],i);
                else c[x]=i;    //除割点外,其他点仅属于 1 个 v-DCC 
            } 
        //缩点之后的森林,点数为 num,边数为 cnt2/2 
        //编号 1~tot 的为原图的 v-DCC,编号 >tot 的为原图割点
         for(int i=2;i<cnt2;i+=2)
             printf("%lld %lld
    ",to2[i^1],to2[i]);
         return 0;
    }
    
    越自律,越自由
  • 相关阅读:
    ##JDBC
    《人月神话》阅读笔记(三)
    《人月神话》阅读笔记(二)
    《人月神话》阅读笔记(一)
    记账小软件开发(网页版)(四)
    记账小软件开发(网页版)(三)
    记账小软件开发(网页版)(二)
    记账小软件开发(网页版)(一)
    课程信息管理系统
    Java课程作业之动手动脑(六)
  • 原文地址:https://www.cnblogs.com/ha-chuochuo/p/13601355.html
Copyright © 2011-2022 走看看