图的连通性之割点与桥

在无向图中:

割点:无向连通图中,去掉一个顶点及和他相邻的所有边,图中的连通分量增加,则该顶点称为割点。

桥:无向连通图中,去掉一条边,图中的连通分量增加,则这条边称为割边或桥。

 

双连通分量(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;
}
View Code

 

 

点双连通分量:

 

 

 

 

posted @ 2019-09-03 00:43  *Zzz  阅读(813)  评论(0)    收藏  举报