双联通分量、强连通分量、割点、桥板子

ver.2017.11.8

me的同学发现了me板子里的小bug,然后me就默默的过来修正啦qwq
十分感谢MaxMercer
具体更正的错误有:
1.桥板子if( lowv > lowu )更正为if( lowv > dfn[u] )
(这个位置好像上一次me才修改过,好像手残了…Emmmmm)

ver.2017.10.13

me发现了板子里的一些小错误,希望之前看过me板子的人不要被me误导了qwq,果咩果咩(跪)
具体更正的错误有:
1.割点板子:pre数组更改为dfn数组
2.割点板子:if(v==fa)修改为if(v==f)
3.桥板子:if(lowv < dfn[u])修改为if(lowv > dfn[u])
4.去掉了强连通板子里不必要的传参

补全有:
1.桥板子:新增了关于判断走回边(v==fa)的正确性和使用范围的注释,这个地方原来写的不够严谨


割点

割点就是,它以下的点最终只能返回他自己,不能返回到它以上的点,即for each v:if(low[v] >= dfn[u]) iscut[u] = true
需要注意的是,root(1号节点)一开始一定是为根的,因为1号节点的dfn最小,其他点的low无论如何也不能比dfn[1]还小。因此需要判断1号点到底有几个”儿子”,如果只有一个则不行。
注意这个”儿子”之间一定是不连通的,不然就会被其他点dfs到,从而不会统计进child

#include 
#include 
#include 
using namespace std ;

int N , M , head[300005] , tp ;
struct Path{
    int pre , to ;
}p[600005] ;

void In ( int t1 , int t2 ){
    p[++tp].pre = head[t1] ; 
    p[ head[t1] = tp ].to = t2 ;
}

int dfn[300005] , dfs_c ;
bool iscut[300005] ;

int dfs( int u , int f ){
    //其实"low"这个东西,既可以写成数组形式,也可以只是作为一个局部变量
    //需要用到"low"值的情况十分少,只有在遍历到没有访问过的点时,当前层会用到下一层的"low"
    //其余的操作几乎都靠dfn,因此low用局部变量也是完全可行的 
    int lowu = dfn[u] = ++dfs_c , child = 0 ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        if( !dfn[v] ){
            child ++ ;
            int lowv = dfs( v , u ) ;
            lowu = min( lowu , lowv ) ;
            if( lowv >= dfn[u] ) iscut[u] = true ;
        } else if( dfn[v] < dfn[u] )
            lowu = min ( lowu , dfn[v] ) ;
    }
    if( f < 0 && child == 1 ) iscut[u] = 0 ;
    return lowu ;
}

void Cut_vertex() {
    memset( dfn , 0 , sizeof( dfn ) ) ;
    memset( iscut , false , sizeof( iscut ) ) ;
    dfs_c = 0 ;
    //这个for循环,依据题意而定
    //按理说,还是求一个连通图的割点比较符合生活常理hhhh 
    for( int i = 1 ; i <= N ; i ++ )
        if( !dfn[i] ) dfs( i , -1 ) ;
}

int main(){
    int t1 , t2 ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d%d" , &t1 , &t2 ) ;
        In( t1 , t2 ) ; In( t2 , t1 ) ;
    }
    Cut_vertex() ;
}

在一张图中,如果说在去掉一条边之后,这张图变得不连通,那么这条边被称为桥。
设这个桥为u->v,在dfs这个图的时候,v所能到的点,最终都只能指回v而不能指回u点以及u以上的点。
即low[v]>dfn[u]

#include 
#include 
#include 
using namespace std ;

int N , M , head[300005] , tp = 1 ;
struct Path{
    int pre , to ;
}p[600005] ;

void In( int t1 , int t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
}

int dfn[300005] , dfs_c , sta[300005] , topp ;
bool isbge[300005] ;

int dfs( int u , int f ){
    int lowu = dfn[u] = ++dfs_c ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( v == f ) continue ;
        //如果有重边,那么这里需要判反向边而不是判fa节点
        if( !dfn[v] ){
            int lowv = dfs( v , u ) ;
            lowu = min( lowu , lowv ) ;
            //u->v且v以下能到的最小的dfn都比u的dfn大
            //相当于是v以下最多只能连接到v,而不能连接到u,u->v即桥 
            if( lowv > dfn[u] ) 
                isbge[i] = isbge[i^1] = true ;//正反边都标记为桥,tp初值设为1 
        } else if( dfn[u] < dfn[v] )
            lowu = min( lowu , dfn[v] ) ;
    }
    return lowu ;
}

void Bridge_(){
    memset( dfn , 0 , sizeof ( dfn ) ) ;
    memset( isbge , false , sizeof( isbge ) ) ;
    dfs_c = 0 ;
    for( int i = 1 ; i <= N ; i ++ )
        if( !dfn[i] ) dfs( i , -1 ) ;
}

int main(){
    int t1 , t2 ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d%d" , &t1 , &t2 ) ;
        In( t1 , t2 ) ; In( t2 , t1 ) ;
    }
    Bridge_() ;
}

边双连通

边双连通就是,在dfs的时候不经过桥就好了,写法就是桥再加一个dfs。然后用vector存即可。

强连通

low[u] = min( low[u] , dfn[v] ) 的时候,需要保证v是个”灰点”(在栈中),其他的没有什么特别需要的地方

#include 
#include 
#include 
using namespace std ;

int N , M , head[300005] , tp ;
struct Path{
    int pre , to ;
}p[600005] ;

void In( int t1 , int t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
}

int Scc[300005] , Scc_cnt , dfn[300005] , low[300005] , dfs_c ;
int sta[300005] , topp ;

int dfs( int u ){
    dfn[u] = low[u] = ++ dfs_c ;
    sta[++topp] = u ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        if( !dfn[v] ) low[u] = min( low[u] , dfs( v ) ) ;
        else if( !Scc[v] ) low[u] = min( low[u] , dfn[v] ) ;//当一个节点还没有获取Scc编号时,相当于该节点还在栈中
                                                            //这样可以节省一个insta(bool型数组,true表示v点在栈中)数组
    }
    if( low[u] == dfn[u] ){
        Scc_cnt ++ ;
        while( 1 ){
            int x = sta[topp--] ;
            Scc[x] = Scc_cnt ;
            if( x == u ) break ;
        }
    }
    return low[u] ;
}

void Scc_(){
    //此处不需要memset"low"数组 
    //因为在到达一个还没有遍历过的点v时,low[v]无论有什么值,都会进入下一层dfs从而被覆盖掉
    memset( dfn , 0 , sizeof( dfn ) ) ;
    //需要memset"dfn"数组
    //因为每到达一个节点v时,都需要依靠dfn[v]是否有值来判断该点是否被到达过 
    memset( Scc , 0 , sizeof( Scc ) ) ;
    dfs_c = Scc_cnt = 0 ;
    for( int i = 1 ; i <= N ; i ++ )
        if( !dfn[i] ) dfs( i ) ;
}

int main(){
    int t1 , t2 ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d%d" , &t1 , &t2 ) ;
        In( t1 , t2 ) ; In( t2 , t1 ) ;
    }
    Scc_();
}

点-双联通

参见刘汝佳的蓝书。
因为一张图中,每条边只会属于一个点双联通分量,因此在入栈的时候,是将边入栈。标记既可以打在边上,也可以打在点上。
需要注意,割点可能同属多个双连通分量,因此割点的标记可能需要特殊的判断,也可以直接用vector存下每个双联通分量的所有点。

#include 
#include 
#include 
using namespace std ;

int N , M , tp = 1 , head[300005] ;
struct Path{
    int fr , pre , to ; 
}p[600005] ;

void In( int t1 , int t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
    p[tp].fr = t1 ;
}

int dfn[300005] , Bcc[600005] , dfs_c , Bcc_cnt , sta[300005] , topp ;
bool iscut[300005] ;

int dfs( int u , int f ){
    int lowu =  dfn[u] = ++dfs_c , child = 0 ;
    for( int i = head[u] ; i ; i = p[i].pre ) {
        int v = p[i].to ;
        if( v == f ) continue ;
        if( !dfn[v] ){
            sta[++topp] = i ; child ++ ;
            int lowv = dfs( v , u ) ;
            lowu = min( lowu , lowv ) ;
            if( lowv >= dfn[u] ){
                iscut[u] = true ; Bcc_cnt ++ ;
                while( 1 ){//在每一个割点求出之后需要立刻出栈判断双联通分量 
                    int x = sta[topp--] ;
                    Bcc[x] = Bcc[x^1] = Bcc_cnt ;
                    if( x == i ) break ;
                }
                //我的写法是直接标记边属于哪个Bcc,为了使用方便,正反都标,这样的话tp需要从1开始
                //当然也可以只标一条,反正点都被包括在内了

                //刘汝佳的写法是用vector存下每个Bcc的点,以及每个点属于哪个Bcc
                /*-------------------------------------------------------------------------
                while(1){
                    int x = sta[topp--] ;
                    if( bccno[ P[x].fr ] != Bcc_cnt ) {
                        bcc[ Bcc_cnt ].push_back( P[x].fr ) ; bccno[ P[x].fr ] = Bcc_cnt ;
                    }
                    if( bccno[ P[x].to ] != Bcc_cnt ){
                        bcc[ Bcc_cnt ].push_back( P[x].to ) ; bccno[ P[x].to ] = Bcc_cnt ;
                    }
                    if( P[x].fr == u && P[x].to == v ) break ;
                }
                -------------------------------------------------------------------------*/
                //中间看起来很冗杂的判断就是为了防止一个点重复被加入vector
                //割点因为同时属于很多Bcc,因此它的bccno没有意义 
            }
        } else if( dfn[v] < dfn[u] ){
            sta[++topp] = i ;
            lowu = min( lowu , dfn[v] ) ;
        }
    }
    if( f < 0 && child == 1 ) iscut[u] = 0 ;
    return lowu ;
}

void Bcc_(){
    memset( dfn , 0 , sizeof ( dfn ) ) ;
    memset( Bcc , 0 , sizeof ( Bcc ) ) ;
    memset( iscut , 0 , sizeof( iscut ) ) ;
    dfs_c = Bcc_cnt = 0 ;
    for( int i = 1 ; i <= N ; i ++ )
        if( !dfn[i] ) dfs( i , -1 ) ;
}

int main(){
    int t1 , t2 ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 ; i <= M ; i ++ ){
        scanf( "%d%d" , &t1 , &t2 ) ;
        In( t1 , t2 ) ; In ( t2 , t1 ) ;
    }
    Bcc_() ;
}

你可能感兴趣的:(知识板块+模板,双,强联通,and,割点桥,浅谈,and,小结)