Loading... > 双连通分量又分点双连通分量和边双连通分量两种。若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。一个无向图中的每一个极大点(边)双连通子图称作此无向图的点(边)双连通分量。——来自360百科 <!--more--> * * * #### 边双联通分量(e-DCC)的求法 计算e-DCC只需要把原无向图中所有的桥删去后,会分成若干连通块,每一个连通块都是一个边双联通分量。 一般可以先使用Tarjan求出所有桥,再执行一次dfs,划分出每个连通块。 c\[x\]代表x点所处的边连通分量编号。 #### e-DCC的缩点 把每个e-DCC看做一个节点,把桥看做连接$c\[x\]$和$c\[y\]$的无向边,产生一棵树。 #### 代码如下 ```cpp #include<bits/stdc++.h> using namespace std; const int SIZE=100009; struct Edge{ int to,next; }e[SIZE*2],ec[SIZE*2]; int head[SIZE],dfn[SIZE],low[SIZE],num,tot,dcc,c[SIZE]; int headc[SIZE],totc; bool bridge[SIZE]; int n,m; void add(int x,int y){ e[++tot].to=y; e[tot].next=head[x]; head[x]=tot; } void add_c(int x,int y){ ec[++totc].to=y; ec[totc].next=head[x]; headc[x]=totc; } void tarjan(int x,int in_edge){ dfn[x]=low[x]=++num; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(!dfn[y]){ tarjan(y,i); low[x]=min(low[x],low[y]); if(dfn[x]<low[y]) 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=e[i].next){ int y=e[i].to; if(c[y]||bridge[i]) continue; dfs(y); } } int main(){ cin>>n>>m; tot=1; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; add(x,y),add(y,x); } for(int i=1;i<=n;i++){ if(!dfn[i]) tarjan(i,0); } cout<<"桥:"<<endl; for(int i=2;i<tot;i+=2){ if(bridge[i]) cout<<e[i].to<<" "<<e[i^1].to<<endl; } for(int i=1;i<=n;i++){ if(!c[i]) dcc++,dfs(i); } for(int i=1;i<=n;i++){ printf("%d属于第%d个dcc\n",i,c[i]); } //缩点 totc=1; for(int i=2;i<=tot;i++){ int x=e[i^1].to,y=e[i].to; if(c[x]==c[y]) continue; add_c(c[x],c[y]); } printf("缩点后共%d个节点,%d条边\n",dcc,totc/2); for(int i=2;i<totc;i+=2){ cout<<ec[i^1].to<<" "<<ec[i].to<<endl; } return 0; } ``` * * * #### 点双连通分量(v-DCC)的求法 求出v-DCC,需要维护一个栈,按照如下方法维护栈: 1. 当一个节点被访问时,将它压入栈。 2. 当判定条件$dfn\[x\]\\leq low\[y\]$成立时,无论x是否为根,都需要: 1. 从栈顶不断弹出点,直至y被弹出。 2. 弹出的所有节点与x构成一个v-DCC。 $vector dcc\[i\]$表示第$i$个v-DCC的所有节点 #### v-DCC的缩点 设图中有n个v-DCC,m个割点,这样缩点后形成一张n+m的树。把每个割点与和它相邻的v-DCC相连接。 #### 代码如下 ```cpp #include<bits/stdc++.h> using namespace std; const int SIZE=100010; struct Edge{ int to,next; }e[SIZE*2]; int head[SIZE],dfn[SIZE],low[SIZE],strck[SIZE],num,tot,sta,cnt; int n,m,root; bool cut[SIZE]; vector<int> dcc[SIZE]; Edge ec[SIZE*2]; int headc[SIZE],totc,tree_id[SIZE]; void add(int x,int y){ e[++tot].to=y; e[tot].next=head[x]; head[x]=tot; } void add_c(int x,int y){ ec[++totc].to=y; ec[totc].next=headc[x]; headc[x]=totc; } void tarjan(int x){ strck[++sta]=x; dfn[x]=low[x]=++num; int flag=0; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); if(dfn[x]<=low[y]){ flag++; if(x!=root||flag>1) cut[x]=1; int z; cnt++; do{ z=strck[sta--]; dcc[cnt].push_back(z); }while(z!=y); dcc[cnt].push_back(x); } } else low[x]=min(low[x],dfn[y]); } } int main(){ cin>>n>>m; for(int i=1;i<=m;i++){ int x,y; cin>>x>>y; add(x,y),add(y,x); } for(int i=1;i<=n;i++){ if(!dfn[i]) root=i,tarjan(i); } printf("有%d个DCC\n",cnt); for(int i=1;i<=cnt;i++){ printf("v-DCC #%d:",i); //cout<<dcc[i].size(); for(int j=0;j<dcc[i].size();j++){ printf("%d ",dcc[i][j]); } cout<<endl; } int treec=cnt; for(int i=1;i<=n;i++){ if(cut[i]) tree_id[i]=++treec; } totc=1; for(int i=1;i<=cnt;i++){ for(int j=0;j<dcc[i].size();j++){ if(!cut[dcc[i][j]]) continue; add_c(i,tree_id[dcc[i][j]]); add_c(tree_id[dcc[i][j]],i); } } printf("缩点后共有%d个点,%d条边\n",treec,totc/2); printf("编号在[1,%d]的为原图v-DCC,编号大于%d的为原图割点。\n",cnt,cnt); for(int i=2;i<totc;i+=2){ printf("%d %d\n",ec[i^1].to,ec[i].to); } return 0; } ``` Last modification:March 14, 2020 © Allow specification reprint Like 如果觉得我的文章对你有用,请随意赞赏