构造双连通图:一个有桥的连通图,如何把它通过加边变成边双连通图?
一个有桥的连通图,如何把它通过加边变成边双连通图?方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
题目: http://poj.org/problem?id=3352
题意:给你一个连通的无向图,现在问你最少在该图中添加几条边,能使得该图变成边双连通图?
我没有搞懂为什么至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,暂且记住吧。
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #include <stack> #define N 10010 using namespace std; struct node { int x,y,w,next,flag; } eg[2*N]; stack<int>q; int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][2],tp; int cnt,de[N]; void init() { tt=0; ti=0; tp=0; cnt=0; memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(be,0,sizeof(be)); memset(de,0,sizeof(de)); memset(bridge,0,sizeof(bridge)); while(!q.empty()) q.pop(); } void add(int xx,int yy) { eg[tt].x=xx; eg[tt].y=yy; eg[tt].next=head[xx]; head[xx]=tt++; } void tarjan(int u,int fa) { dfn[u]=low[u]=++ti; q.push(u); for(int i=head[u]; i!=-1; i=eg[i].next) { int v=eg[i].y; if(v==fa) continue; if(!dfn[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支 { bridge[tp][0]=u; bridge[tp++][1]=v; ++cnt; int w; do { w=q.top(); q.pop(); be[w]=cnt; } while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量 } } else { low[u]=min(dfn[v],low[u]); } } } int main() { int xx,yy; while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=1; i<=m; i++) { scanf("%d%d",&xx,&yy); add(xx,yy); add(yy,xx); } tarjan(1,-1); if(!q.empty()) { ++cnt; int w; do { w=q.top(); q.pop(); be[w]=cnt; } while(w!=1); } for(int i=0; i<tp; i++) { int u=bridge[i][0]; int v=bridge[i][1]; de[be[u]]++; de[be[v]]++; //统计缩点后的的度 } /*for(int i=0;i<tp;i++) { printf("bridge==%d %d\n",bridge[i][0],bridge[i][1]); } printf("cnt==%d\n",cnt);*/ int leaf=0; for(int i=1; i<=cnt; i++) { if(de[i]==1) leaf++; } printf("%d\n",(leaf+1)/2); } return 0; }
第一次交的,需要用数组f[N]防止跨边,我感觉实际上这样是没用的,但大家代码都这么写我就贴一下吧,无向图是没有跨边的。
#include <iostream> #include <stdio.h> #include <string.h> #include <algorithm> #include <stack> #define N 10010 using namespace std; struct node { int x,y,w,next,flag; } eg[2*N]; stack<int>q; int tt,head[N],dfn[N],low[N],ti,n,m,be[N],bridge[N][2],tp; int cnt,de[N],f[N]; void init() { tt=0; ti=0; tp=0; cnt=0; memset(head,-1,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(be,0,sizeof(be)); memset(de,0,sizeof(de)); memset(bridge,0,sizeof(bridge)); memset(f,0,sizeof(f)); while(!q.empty()) q.pop(); } void add(int xx,int yy) { eg[tt].x=xx; eg[tt].y=yy; eg[tt].next=head[xx]; head[xx]=tt++; } void tarjan(int u,int fa) { dfn[u]=low[u]=++ti; q.push(u); f[u]=1; for(int i=head[u]; i!=-1; i=eg[i].next) { int v=eg[i].y; if(v==fa) continue; if(!dfn[v]) { tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>dfn[u])//边(u,v)为桥,可以统计一个边连通分支 { bridge[tp][0]=u; bridge[tp++][1]=v; ++cnt; int w; do { w=q.top(); q.pop(); f[w]=0; be[w]=cnt; } while(w!=v);//注意点u并没有出栈,因为点u属于另一个边连通分量 } } else if(f[v]) { low[u]=min(dfn[v],low[u]); } } } int main() { int xx,yy; while(scanf("%d%d",&n,&m)!=EOF) { init(); for(int i=1; i<=m; i++) { scanf("%d%d",&xx,&yy); add(xx,yy); add(yy,xx); } tarjan(1,-1); if(!q.empty()) { ++cnt; int w; do { w=q.top(); q.pop(); be[w]=cnt; } while(w!=1); } for(int i=0;i<tp;i++) { int u=bridge[i][0]; int v=bridge[i][1]; de[be[u]]++; de[be[v]]++; //统计缩点后的的度 } /*for(int i=0;i<tp;i++) { printf("bridge==%d %d\n",bridge[i][0],bridge[i][1]); } printf("cnt==%d\n",cnt);*/ int leaf=0; for(int i=1;i<=cnt;i++) { if(de[i]==1) leaf++; } printf("%d\n",(leaf+1)/2); } return 0; }