图的连通性之割点与桥
在无向图中:
割点:无向连通图中,去掉一个顶点及和他相邻的所有边,图中的连通分量增加,则该顶点称为割点。
桥:无向连通图中,去掉一条边,图中的连通分量增加,则这条边称为割边或桥。
双连通分量(BCC):
边双连通:无向图中,任意去掉一条边都不会改变此图的连通性,即不存在桥,就是边双连通图。
(如果是个环,则任意两点都有两条路到达对方,那就没有桥了,就是边双连通分量)
(双连通分量就可以简单理解为若干个简单环连在一起,至少有一个公共节点)
(不在双连通分量中的边就是桥)

点双连通:无向图中,任意去掉一个节点都不会改变此图的连通性,即不存在割点,就是边点双连通图
(点双连通分量,就是若干个环有一条边是重合的)



要从编号0开始记录边

例题:POJ - 3177
分析:一个有桥的连通图,如何把它通过加边变成边双连通图?
方法为首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。
其实,本题数据保证是一个连通图,那么桥=缩点数-1;
统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
#include <cstdio> #include <cstring> #include <algorithm> #define maxn 601 using namespace std; int dfn[maxn],low[maxn],bcc[maxn],stk[maxn],index=0,bccnum=0,top=0,ef[maxn]; int cnt=0; struct node{ int to,next; }edge[maxn]; int head[maxn]; void Tarjan(int root){ dfn[root] = low[root] = ++index; stk[++top] = root; //节点入栈 for(int i=head[root]; i!=-1; i=edge[i].next){ int v =edge[i].to; if(ef[i]) continue; ef[i] = ef[i^1] = 1; //无向图双向边 ^1是反边 边下标要从1开始 if(!dfn[v]){ Tarjan(v); low[root] = min(low[root],low[v]); } else low[root] = min(low[root],dfn[v]); } if(low[root] == dfn[root]){ bccnum++; for(;;){ int x = stk[top--]; bcc[x] = bccnum; if(x==root) break; } } } void add(int u,int v){ edge[cnt].to = v; edge[cnt].next = head[u]; head[u] = cnt++; } int in[maxn]; void init(){ top=0; memset(head,-1,sizeof(head)); memset(in,0,sizeof(in)); } int main(){ int n,m; init(); scanf("%d%d",&n,&m); int x,y; for(int i=1; i<=m; i++){ scanf("%d%d",&x,&y); add(x,y); add(y,x); } for(int i=1; i<=n; i++){ if(!dfn[i]) Tarjan(i); } for(int i=1; i<=n; i++){ for(int j=head[i]; j!=-1; j=edge[j].next){ if(bcc[i] != bcc[ edge[j].to ]){ in[ bcc[i] ]++; } } } int ans=0; for(int i=1; i<=bccnum; i++){ if(in[i]==1) ans++; } printf("%d\n", (ans+1)/2); return 0; }
点双连通分量:


浙公网安备 33010602011771号