上一篇讲了如何应用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的缩点