[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\n",toc(i^1),toc(i));
    return 0;
}

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

猜你喜欢

转载自www.cnblogs.com/light-house/p/11766133.html
今日推荐