这个算法是来源下面的博客中写的如何求桥(我把它改造成求割点,但有一些问题,还望 dalao 们指正):
博客中说的求桥的方法主要是基于当且仅当一个树边 (u,v) 中,两节点 u,v 中 dfs 序较大的节点的任何后代节点的都没有与两节点的祖先节点通过前向边直接相连,则树边 (u,v) 是桥。然后给出了一种动态规划计算 dp(u), 即一个点 u 和它的父节点被多少前向边跨越的方法,转移方程为:(这里 up(u) 是从 u 节点开始的前向边的数量,down(u) 是指到达 u 节点的前向边的数量)
dp(u)=up(u)−down(u)+v∈son(u)∑dp(v)这样求出 dp(u) 后,对于 dp(u) 为 0 的节点,其与父节点的树边就是桥。
(不用考虑横向边的原因是由于《算法导论》中在讲图算法中的深度优先搜索时证明了对于无向图不存在横向边。)
我认为求出桥以后,只有桥两边的节点才可能成为割点,所以对于每一个 dp(u)=0 先判断其是不是叶子节点,然后在特判一下根节点,但是这样似乎只有 32 pts 的样子……
下面是代码:
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int maxn = 2e4 + 10;
const int maxm = 2e5 + 10;
int n, m, head[maxn], to[maxm], next[maxm], tot = 0;
int dfn[maxn], f[maxn], size[maxn], par[maxn], timer = 0;
bool state[maxn];
std::vector<int> ans;
inline void add_edge(int u, int v)
{
to[++tot] = v;
next[tot] = head[u];
head[u] = tot;
}
void dfs(int u, int pre)
{
par[u] = pre, f[u] = 0, size[u] = 1, dfn[u] = ++timer;
int up = 0, down = 0;
//up down 与上文定义相同
for (int c = head[u], v; c; c = next[c])
{
v = to[c];
if (!dfn[v])
{
dfs(v, u);
f[u] += f[v], size[u] += size[v];
}
else
{
//下面的 if 是特判掉树边
if (dfn[v] < dfn[u] - 1)
++up;
if (dfn[v] > dfn[u] + 1)
++down;
}
}
f[u] += up - down;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; ++i)
{
scanf("%d%d", &u, &v);
add_edge(u, v), add_edge(v, u);
}
for (int i = 1, cnt; i <= n; ++i)
{
if (!dfn[i])
{
cnt = 0;
for (int c = head[i]; c; c = next[c])
++cnt;
dfs(i, 0);
if (cnt <= 1)//根节点特判
state[i] = true;
}
}
for (int i = 1; i <= n; ++i)
{
if (f[i] == 0)
{
if (!state[i] && size[i] > 1)
{
ans.push_back(i);
state[i] = true;
}
if (!state[par[i]] && par[i])
{
ans.push_back(par[i]);
state[par[i]] = true;
}
//state 记录节点是否已统计或是否合法
}
}
std::sort(ans.begin(), ans.end());
printf("%u\n", ans.size());
for (auto c : ans)
printf("%d ", c);
return 0;
}