#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline LL read () {
LL res = 0 ;
int f (1) ;
char ch = getchar ();
while (!isdigit(ch)) {
if (ch == '-') f = -1 ;
ch = getchar();
}
while (isdigit(ch)) res = (res << 1) + (res << 3) + (ch ^ 48),ch = getchar();
return res * f ;
}
const int N = 1000000+10;
int a[N] , nxt [N] , head[N] , dfn[N] , low[N] , cnt , k ;
bool cut[N] , bst[N] ;
inline void Add (int x ,int y) {
a[++k] = y ; nxt[k] = head[x]; head[x] = k ; return ;
}
inline void tarjan (int u,int mr) {
int rc = 0;
dfn[u] = low[u] = ++ cnt ;
for ( register int p = head[u] ; p ; p = nxt[p] ) {
int v = a[p] ;
if (! dfn [v]) {
tarjan ( v , mr ) ;
low[u] = min ( low[u] , low[v] );
if (low[v] >= dfn[u] and u != mr) cut[u]=true;
if (u == mr) rc ++ ;
}
low[u] = min ( low[u] , dfn[v] ) ;
}
if (u == mr and rc >= 2) cut[mr] = true;
}
signed main() {
int n = read() ;
int m = read() ;
int ans = 0 ;
for ( register int i = 1 ;i <= m ; i ++) {
int x = read() ;
int y = read() ;
Add (x,y) ; Add (y,x) ;
}
for ( register int i = 1 ;i <= n ; i ++)
if ( ! dfn[i] ) tarjan ( i , i ) ;
for ( register int i = 1 ;i <= n ; i ++)
if ( cut[i] ) ans ++ ;
cout << ans << endl ;
for ( register int i = 1 ;i <= n ; i ++)
if ( cut[i] ) cout << i << ' ' ;
return 0 ;
}
首先tarjan求割点的重点就是dfn和low数组的理解。
dfn[i]就是时间戳,即在什么时刻搜索到了点i,
low[i]则是i点能回溯到的dfn最小的祖先,
搜索的时候判断一下当对于点x存在儿子节点y,使得dfn[x]<=low[y]则x一定是割点。
因为只要x的子节点不能回溯到x的上面,就是没有返祖边超过x点,那么割掉x就能造成不连通了
好啦,基本算法介绍完,就要讲几个问题了。
首先,为什么此处
low[a]=min(low[a],dfn[p]);
不能写作
low[a]=min(low[a],low[p]);
在我的理解,由于此处是一张无向图,我们有双向建了边,导致节点可以回溯到它的父节点;
而如果从它的父节点或其父节点的另一棵子树上有向上很多的返祖边,
这时把子节点的low值赋为父节点的low,就可能导致其low==其父节点low<其父节点dfn,
从而使本该是割点的点被忽视了,答案就少了,所以就wa了。
另外本题还有几个注意点:
-
给的图不一定是连通图,即求每个联通块的割顶
-
输出格式别看错了2333
-
链式前向星开边要2倍