边双连通分支

/*
求边双连通分支:跑一遍求割点与桥的Tarjan得到该图的割点和桥,去掉桥,其余连通分支就是边连通分支了,边连通分支数是桥数+1。
                边连通分支就是去掉最少两条边才能将该图划分为两个部分的图,桥就是一个图被去掉一条边就能变成两个子图的那一条边, 
构造边双连通分支:
                把双连通子图收缩为一个点,形成一个树,需要加边数量是(leaf+1)/ 2 。即不断把最近公共祖先最远的两个叶节点连接一条边 
*/

//poj3177


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>                
#include<map>
using namespace std ;

const int maxn = 5000 + 10 ;
const int maxm = 20000 + 10 ;
struct Edge{
    int to , next ;
    //该边是否为桥 
    bool cut ;
}edge[maxm];
int head[maxn] , tot ;

//Low[u]表示u所能追溯到的在栈中最早的节点,该Low数组和求割点与桥的Low数组定义不一样 
int Low[maxn] , DFN[maxn] , Stack[maxn] , Belong[maxn] ;
int Index , top ;
//边双连通块数 
int block ;
bool Instack[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 ;

    for( int i = head[u] ; i != -1 ;i = edge[i].next ){
        v = edge[i].to ;
        if( v == pre) continue ;
        //树枝边 
        if( !DFN[v  ]){
            //回溯从叶子结点开始处理,一直到根节点 
            Tarjan( v , u ) ;
            //对于树枝边(u,v),修改low[u]之前一定有何v相连的回向边修改了 Low[v],造成v的Low数值变小 
            if( Low[u] > Low[v] )
                Low[u] = Low[v] ;
            //桥边,回溯过后v所能追溯到的标号最小的节点依旧比u的编号大,这时Low[v] = DFN[v] ; 
            if( Low[v] > DFN[u]){
                bridge ++ ;
                edge[i].cut = true ;
                edge[i ^ 1].cut = true ;
            } 
        }
        //(u,v)回向边,修改u所能追溯到的编号最小的节点 
        else if( Instack[v] && Low[u] > DFN[v])
            Low[u] = DFN[v] ;
    }
    //u节点是该连通分量的根节点 
    if( Low[u] == DFN[u]){
        block ++ ;
        do{
            v = Stack[ -- top ] ;
            Instack[v] = false ;
            Belong[v] = block ;
        }
        while( v != u ) ;
    }
}

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

//缩点后形成树,每个点的度数 
int du[maxn] ;
void solve( int n ){
    memset( DFN , 0 ,sizeof( DFN )) ;
    memset( Instack , false , sizeof( Instack )) ;
    Index = top = block = 0 ;
    Tarjan( 1 , 0 ) ;


    int ans = 0 ;
    memset( du , 0 , sizeof( du )) ;
    for(int i = 1 ;i<=n ;i++){
        for(int j = head[i] ;j!=-1 ; j=edge[j].next )
            if( edge[j].cut )
                du[Belong [i]] ++;
    }
    for(int i = 1 ;i<=block ; i++)
        if( du [i] == 1)
            ans ++;
    printf("%d\n" , ( ans + 1) / 2 ) ;
}

int main(){
    int n , m ;
    int u , v ;
    while( scanf("%d%d" , & n , & m ) == 2){
        ini() ;
        while( m -- ){
            scanf("%d%d" , &u , & v ) ;
            addedge( u , v ) ;
            addedge( v , u ) ;
        }
        solve( n ) ;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值