回家
本质是\(Tarjan\)的一个板子,考试的时候看到像个板子太开心了,导致想少了。
当时想求出边双之后看边双之间的连边,他们的连接点就是必经点。
但是我也不知道为什么当时那么智障。
但是其实容易知道所有的必经点都是割点,但有些割点并不是必经点。
但是可以先求割点后来判断。
求出点双之后缩点,易知道缩完之后是一棵树。
这个树是一个神奇的树,假设原图中有\(Z\)个点双,\(T\)个割点。
那么这个树里面包含这\(Z+T\)个点。
树上两点路径唯一,直接记录\(1~n\)的路径中出现的割点,不用鸟遇到的点双。
然后输出就行了。
还有一种思路,对\(Tarjan\)进行改造。
来自学长的方法:只有当\(dfn [y]<=dfn[n]\)的时候在判断为割点,这样可以保证是祖先割的。
然后dfs的时候直接遇到割点输出就行
void tj( int x ){
int fl = 0 ;
dfn [x] = low [x] = ++ gnt ;
for( R i = head [x] ; ~i ; i = a [i].next ){
int y = a [i].to ;
if( !dfn [y] ){
tj ( y ) ; fa [y] = x ;
low [x] = min( low [x] , low [y] ) ;
if( low [y] >= dfn [x] && dfn [n] >= dfn [y] ){
fl ++ ;
if( fl > 1 || x != root ) g [x] = 1 ;
}
}
else low [x] = min( low [x] , dfn [y] ) ;
}
}
//This in "main"
int x = n ;
while( x != 1 ){
int f = fa [x] ;
if( f != 1 && f != n && g [f] ) ans ++ , st [++ top] = f ;
x = f ;
}
还有一种和他等价的写法,就是\(Tarjan\)的时候只记录\(dfn,low\),在\(dfs\)的时候根据割点判定法则来判断是否是祖先割的
void tj( int x ){
dfn [x] = low [x] = ++ gnt ;
for( R i = head [x] ; ~i ; i = a [i].next ){
int y = a [i].to ;
if( !dfn [y] ){
tj ( y ) ; fa [y] = x ;
low [x] = min( low [x] , low [y] ) ;
}
else low [x] = min( low [x] , dfn [y] ) ;
}
}
//This in main
int x = n ;
while( x != 1 ){
int f = fa [x] ;
// printf( "F%ld %ld %ld %ld\n" , x , f , low [x] , dfn [f]) ;
if( f != 1 && f != n && low [x] >= dfn [f] ) ans ++ , st [++ top] = f ;
x = f ;
}
还有信哥提供的一种方法:在\(Tarjan\)回溯的时候记录是否为搜索路上的点,如果是并且是割点,再记录答案。
ex [n] =1 ;
void tj( int x ){
int fl = 0 ;
dfn [x] = low [x] = ++ gnt ;
for( R i = head [x] ; ~i ; i = a [i].next ){
int y = a [i].to ;
if( !dfn [y] ){
tj ( y ) ; fa [y] = x ;
low [x] = min( low [x] , low [y] ) ;
if( low [y] >= dfn [x] ){
fl ++ ;
if( ( fl > 1 || x != root ) && ( ex [y] ) ) g [y] = 1 ;
} ex [x] |= ex [y] ;
}
else low [x] = min( low [x] , dfn [y] ) ;
}
}
方法很多,不胜枚举,主要是对搜索和\(low,dfn\)的理解够不够透彻。
他们在本质上没有区别,就是只对搜索树上的祖先判割。
本人的理解还不够深,加上\(Tarjan\)板子很多,还需要更深的理解。
$The \ light \ has \ betrayed \ me$

浙公网安备 33010602011771号