割点和桥

图的割点、桥、双连通分支知识

割点集合: 在一个无向连通图中,如果有一个顶点集合,当删除这个集合中的顶点和这些顶点关联的边之后,得到的图是多个连通块,则这个顶点集合就是割点集合
割边集合: 如上定义 
图的点连通度:最小割点集合中顶点个数
图的边连通度:最小割边集合中边的个数

点(边)双连通图: 是一个点(边)连通度大于1无向图 
割点:对于一个点连通度为1的无向图,他的割点集合的唯一顶点就是割点
桥  :对于一个边连通度为1的无向图,他的割边集合的唯一边就是桥

双连通子图: 对于一个图G,他的子图g如果是双连通的,那么g就是双连通子图
双连通分支: 图的一个极大双连通子图
块:点双连通分支

求割点:一个顶点是割点,当且仅当该点满足(1)u是树根且u有多余一个子树;(2) u不为树根,且满足存在(u,v)为树枝边(父子边),使得DFS(u)<=Low(V)
求桥  :一条无向边(u,v)是桥,当且仅当(u,v)为树枝边且满足DFS(u)<Low(v) 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
using namespace std ;

const int maxn = 1e4 + 10 ;
const int maxm = 1e5 + 10 ;

struct Edge{
    int to , next ;
    //是否为桥的标记 
    bool cut ;  
}edge[maxm];

int head[maxn] , tot ;

//Low[u]表示u不通过树枝边所能追溯到的次序最小的节点,DFN[u]代表u定点的次序号,他是唯一的并且最大为n 
int Low[maxn] , DFN[maxn] , Stack[maxn] ;
int Index , top ;
bool instack[maxn] ;

bool cut[maxn] ;
//删除一个点后增加的连通块 
int add_block[maxn] ; 
int bridge ;

void addedge(int u , int v ){
    edge[tot].to = v ; edge[tot].next = head[u] ; edge[tot].cut = false ;
    head[u] = tot ++ ;
}

void Tarjan( int u , int pre){
    int v ;
    Low[u] = DFN[u] = ++ Index ;
    Stack[ top ++ ] = u ;
    instack[ u ] = true ;

    int son = 0 ;
    for(int i = head[u] ; i!= -1 ; i= edge[i].next){
        v = edge[i].to ;
        if( v == pre) continue ;
        //(u,v)为树枝边 
        if( !DFN[v] ){
            son ++ ;
            Tarjan( v , u ) ;
            if( Low[u] > Low[v] )
                Low[u] = Low[v] ;
            //一条无向边(u,v)是桥,当且仅当该边是树枝边(!DFN满足),且DFS(u)<Low(v)    
            if( Low[v] > DFN[u]){
                bridge ++ ;
                //是桥( 树枝边 | 后向边 的理解) 
                edge[i].cut = true ;
                //反向边 
                edge[i ^ 1].cut = true ;
            }
            //一个顶点是割点,当且仅当该点满足(1)u是树根且u有多余一个子树;(2) u不为树根,且满足存在(u,v)为树枝边(父子边),使得DFS(u)<=Low(V)
            //u!= pre代表u不是树根
            //u到v连接了一个连通快,则u所连接的连通快+1  
            if( u!=pre && Low[v] >= DFN[u]){
                cut[u] = true ;
                add_block[u] ++ ;
            }
        }
        //(u,v)为回向边,u能够通过v定点追溯到次序号更小的定点 
        else if( Low[u] > DFN[v])
            Low[u] = DFN[v] ;
    }
    //树根,分支数大于1,则u是割点 
    if( u == pre && son > 1) cut[u] = true ;
    //割点连接的连通块个数 
    if( u == pre )
        add_block[u] = son -1 ;
    instack[u] = false ;
    top -- ;
}

//uva796 给出一个无向图按照顺序输出桥
//poj2117 求删除一个点后,图中最多有多少连通块,利用add_block数组,求割点和割点增加的连通块的个数 
void solve( int N ){
    memset( DFN , 0 , sizeof( DFN )) ;
    memset( instack , false , sizeof( instack)) ;
    memset( add_block , 0 , sizeof( add_block )) ;
    memset( cut , false , sizeof( cut )) ;
    Index = top = 0 ;
    bridge =  0 ;

    //从1开始编号,所以输入从0开始的话必须转换成1开始,就需要+1 
    for(int i = 1 ;i<= N ;i++)
        if( !DFN[i ] )
            //对于一个没有访问过的定点,他是根节点 
            //每一次Tarjan都是得到的都是当前图的一个连通快 
            Tarjan( i , i ) ;

    printf("%d critical links\n" , bridge) ;
    vector<pair<int,int> > ans ;

    //注意去重处理 
    for(int u = 1 ;u<=N ;  u++){
        for(int i = head[u] ; i != -1 ; i= edge[i].next)
            //应为要排序,通过edge[i].to>u去掉反向边,也就去重了 
            if( edge[i].cut && edge[i].to > u ){
                ans.push_back( make_pair ( u , edge[i].to )) ;
            }
    }
    sort( ans.begin() , ans.end() ) ;

    //按顺序输出桥
    for(int i = 0 ;i <ans.size() ; i++){
        printf("%d - %d\n" , ans[i].first -1 , ans[i].second -1 ) ;
    } 
    printf("\n") ;
} 

void ini(){
    tot = 0 ;
    memset( head , -1 , sizeof( head )) ;
}

int main(){
    int n ;
    while( scanf("%d" , & n ) == 1){
        ini() ;
        int u , k , v ;
        for(int i = 1 ;i<= n ; i++){
            scanf("%d (%d) " , & u , & k ) ;
            u ++ ;
            //保证正边喝反边相邻,建立无向图
            while( k --){
                scanf("%d" , & v) ;
                v ++ ;
                //注意处理桥和边的问题就需要这要做,否则就会重边 
                if( v <= u ) continue ;
                addedge(u , v) ;
                addedge(v , u ) ; 
            } 
        }
        solve( n ) ;
    }
    return 0 ;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值