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