• [Tarjan系列] 无向图e-DCC和v-DCC的缩点


    上一篇讲了如何应用Tarjan算法求出e-DCC和v-DCC。

    那么这一篇就是e-DCC和v-DCC的应用之一:缩点。

    先讲e-DCC的缩点。

    我们把每一个e-DCC都看成一个节点,把所有桥边(x,y)看成连接编号为c[x]和c[y]的两个e-DCC间的边,这样我们就会得到一棵树或者森林(原图不连通)。给出缩点的代码,这份代码把e-DCC缩点并把生成的树(森林)储存在另一个邻接表中。

    #include<bits/stdc++.h>
    #define N 100010
    using namespace std;
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    struct Edge{
        int nxt,to;
        #define nxt(x) e[x].nxt
        #define to(x) e[x].to
    }e[N<<1];
    struct EdgeC{
        int nxtc,toc;
        #define nxtc(x) ec[x].nxtc
        #define toc(x) ec[x].toc
    }ec[N<<1];
    int head[N],tot=1,n,m,cnt,dfn[N],low[N],c[N],bridge[N],dcc;
    int headc[N],totc=1;
    inline void addedge(int f,int t){
        nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
    }
    inline void addedge_c(int f,int t){
        nxtc(++totc)=headc[f];toc(totc)=t;headc[f]=totc;
    }
    void tarjan(int x,int in_edge){
        dfn[x]=low[x]=++cnt;
        for(int i=head[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])
                    bridge[i]=bridge[i^1]=1;
            }else if(i!=(in_edge^1))
                low[x]=min(low[x],dfn[y]);
        }
    }
    void dfs(int x){
        c[x]=dcc;
        for(int i=head[x];i;i=nxt(i)){
            int y=to(i);
            if(c[y]||bridge[i])continue;
            dfs(y);
        }
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<=m;i++){
            int x=read(),y=read();
            addedge(x,y);addedge(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);
            }
        }
        for(int i=2;i<=tot;i++){
            int x=to(i^1),y=to(i);
            if(c[x]==c[y])continue;
            addedge_c(c[x],c[y]);
        }
            //缩点后的树(森林)的点数为dcc,边数为totc/2
        for(int i=2;i<totc;i++)
            printf("%d %d",toc(i^1),toc(i));
        return 0;
    }

    v-DCC的缩点由于一个割点可能在很多个v-DCC中而更加麻烦,但是我们也有办法缩。

    假设图中有x个割点和y个v-DCC,我们就直接建(x+y)个点的新图。

    每一个v-DCC和割点都作为新图的节点存在。建完后我们让每个割点和包含它的v-DCC连边。

    给出代码:

    #include<bits/stdc++.h>
    #define N 100010
    using namespace std;
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    struct Edge{
        int nxt,to;
        #define nxt(x) e[x].nxt
        #define to(x) e[x].to
    }e[N<<1];
    struct EdgeC{
        int nxtc,toc;
        #define nxtc(x) ec[x].nxtc
        #define toc(x) ec[x].toc
    }ec[N<<1];
    int head[N],tot=1,n,m,rt,dfn[N],low[N],cnt,stk[N],top,num,cut[N];
    int headc[N],totc=1,new_id[N];
    vector<int> dcc[N];
    inline void addedge(int f,int t){
        nxt(++tot)=head[f];to(tot)=t;head[f]=tot;
    }
    inline void addedge_c(int f,int t){
        nxtc(++totc)=headc[f];toc(totc)=t;headc[f]=totc;
    }
    void tarjan(int x){
        dfn[x]=low[x]=++cnt;
        stk[++top]=x;
        if(x==rt && head[x]==0){
            dcc[++num].push_back(x);
            return;
        }
        int flag=0;
        for(int i=head[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!=rt||flag>1)cut[x]=1;
                    num++;int z;
                    do{
                        z=stk[top--];
                        dcc[num].push_back(z);
                    }while(z!=y);
                    dcc[num].push_back(x);
                }
            }else low[x]=min(low[x],dfn[y]);
        }
    }
    int main(){
        n=read();m=read();
        for(int i=1;i<=m;i++){
            int x=read(),y=read();
            addedge(x,y);addedge(y,x);
        }
        for(int i=1;i<=n;i++)
            if(!dfn[i])tarjan(i);
        cnt=num;//给每个割点一个新的编号防止重复,从num+1开始
        for(int i=1;i<=n;i++)
            if(cut[i])new_id[i]=++cnt;
        for(int i=1;i<=cnt;i++){
            for(int j=0;j<dcc[i].size();j++){
                int x=dcc[i][j];
                if(cut[x]){//割点和每个v-DCC连边
                    addedge_c(i,new_id[x]);
                    addedge_c(new_id[x],i);
                }else new_id[x]=i;
            }
        }
            //缩点后的森林(树)点数为cnt,边数为totc/2
        for(int i=2;i<totc;i+=2)
            printf("%d %d
    ",toc(i^1),toc(i));
        return 0;
    }

    下一篇更新Tarjan求有向图的SCC以及SCC的缩点

  • 相关阅读:
    openssl自签发证书
    安装tomcat8 env
    路由信息相关 route 网卡
    安装jdk env
    sublime使用与配置
    docker仓库登录 配置insecure-registries
    harobor私有docker镜像仓库
    git版本回退的两种方式
    git diff命令的使用
    Kali Linux中的自带字典&crunch自建字典
  • 原文地址:https://www.cnblogs.com/light-house/p/11766133.html
Copyright © 2020-2023  润新知