双联通&&强连通&&割点桥板子

来源:互联网 发布:研究生软件辅导机构 编辑:程序博客网 时间:2024/05/22 02:18

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 <cstdio>#include <cstring>#include <algorithm>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 <cstdio>#include <cstring>#include <algorithm>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 <cstdio>#include <cstring>#include <algorithm>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 <cstdio>#include <cstring>#include <algorithm>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_() ;}
阅读全文
2 0
原创粉丝点击