双联通&&强连通&&割点桥板子
来源:互联网 发布:研究生软件辅导机构 编辑:程序博客网 时间: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_() ;}
- 双联通&&强连通&&割点桥板子
- 割点,割边,强联通分量,点双联通分量,边双联通分量
- 割点、割边、强连通分量
- 【图论算法及模版】割点,割边,强联通分量,点双联通分量,边双联通分量
- 【图论算法及模版】割点,割边,强联通分量,点双联通分量,边双联通分量
- 强连通分量(割点)
- BZOJ 1093 最大半联通子图 强连通分量缩点+拓扑排序dp
- poj 2762(弱联通:强连通+缩点+拓扑排序)
- 点连通分量+边连通分量+割点和桥+强连通分量
- 图的强连通分量,块,割点,桥
- 图的强连通分量,块,割点,桥
- 强连通-割点(曾老代码)
- 强连通图_割点_割边(桥)_双向连通分量关系
- 连通图的割点、割边 连通图的割点、割边(桥)、块、缩点,有向图的强连通分量
- 【图论】割点,桥,边双联通
- 割点,割桥,双联通模板总结
- 连通图的割点、割边(桥)、块、缩点,有向图的强连通分量
- 连通图的割点、割边(桥)、块、缩点,有向图的强连通分量
- Nmap在实战中的高级用法
- tomcat非正常关闭解决办法
- matlab max/min/median函数用法以及自定义函数求最大最小值和中位数
- 逻辑右移和算术右移、C 与Java的右移策略
- spring boot 事务管理
- 双联通&&强连通&&割点桥板子
- Happen-Before规则
- mac 下开启任何来源
- 牛客《剑指Offer》-- 合并两个排序的链表
- C++基础-类的基本概念
- 【Ethereum】以太坊ERC20 Token标准完整说明
- linux下cpu压力负载测试
- Java-Collection源码分析(一)——Collection接口和AbstractCollection类
- C#梳理【集合Collection】