何为割点?
简单来说是在一个无向图中,若删除该点,则该无向图将会被分为两部分
如上图所示,结点是一个割点,但是不是割点
此外在这里我们引入 两个概念
例如对上图我们从结点开始遍历,标记时间戳是这样的
再之,我们求得每个结点的值如下
我们可以发现,对结点而言,他们无法回溯到比自己更小的值,即显然他们的父节点为一个割点。
我们用的复杂度遍历图中的每一个结点,并对其的进行初始化
1 | st[u] = 1; |
结点为割点的两种情况:
1、当前结点为根结点,且存在两个以上的子结点
2、当前结点不为根节点,且对于其连接的子结点而言,,说明其儿子无法通过其他结点跑到结点之前
因此,当我们遍历发现该结点为标记过时
1 | if(!st[v]){ |
反之该结点标记过了,并且结点没有往回跑的话,我们如果继续用的话,
以下图为例,
当我们在从跑到时,如果对用的话,那么,此时不满足显然的割点条件,因此
我们需要对标记过的连接值取
至此我们得出该题的解决方案.
代码
#include "bits/stdc++.h"
#define rep(i, n, m) for(int i = n; i <= m; ++i)
using namespace std;
const int N = 8e5 + 10;
int dfn[N], low[N], st[N], ok[N], in = 0;
int h[N], ne[N], e[N], cnt, ans = 0;
void add(int a, int b){
e[cnt] = b, ne[cnt] = h[a], h[a] = cnt++;
}
void tarjan(int u, int fa){
st[u] = 1;
dfn[u] = low[u] = ++ in;
int num = 0;
for(int i = h[u]; ~i; i = ne[i]){
int v = e[i];
if(!st[v]){
num ++;
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(fa != u && low[v] >= dfn[u] && !ok[u]){
ok[u] = 1;
ans ++;
}
}else if(v != fa){
low[u] = min(low[u], dfn[v]);
}
}
if(u == fa && num >= 2 && !ok[u]){
ok[u] = 1;
ans ++;
}
}
int main()
{
//ios::sync_with_stdio(0); cin.tie(0);
memset(h, -1, sizeof h);
memset(ok, 0, sizeof ok);
int n, m; cin >> n >> m;
rep(i, 1, m){
int u, v; cin >> u >> v;
add(u, v), add(v, u);
}
rep(i, 1, n){
if(!st[i]){
in = 0;
tarjan(i, i);
}
}
cout << ans << '\n';
rep(i, 1, n){
if(ok[i]) cout << i << ' ';
}
return 0;
}