在一个无向图中,如果删掉点 $v$ 后图的连通块数量增加,则称点 $v$ 为图的割点。
Tarjan 算法可以在线性时间求无向图的所有割点。
定义
$mathrm{dfn}(u)$ 表示进入节点 $u$ 时的时间(时间截)。
$mathrm{low}(u)$ 表示由节点 $u$ 开始搜索所能到达的点中,在搜索树上是 $u$ 的祖先且 $mathrm{dfn}$ 最小的节点的 $mathrm{dfn}$。
描述
类似于 Tarjan 求强连通分量,大致流程:
-
选定一个节点作为根节点,开始 DFS;
-
初始化当前点的 $mathrm{dfn}$ 和 $mathrm{low}$ 均为当前时间截;
-
遍历当前点 $v$ 的所有邻接点(与 $v$ 相连的点);
-
如果某个邻接点 $u$ 在 DFS 树中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;
-
如果某个邻接点 $u$ 不在 DFS 树中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;
-
对于根节点,如果它在 DFS 树上有两个或更多的子节点,则它为割点;
-
对于一个 DFS 树上的非根节点 $u$,如果存在子节点 $v$,满足 $mathrm{low}(v) geq mathrm{dfn}(u)$,则 $u$ 为割点。
解释
如果某个邻接点 $u$ 在 DFS 树中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;
$mathrm{low}$ 被定义为节点经过最多一条后向边(与 DFS 方向相反,从某个节点指向其某个祖先的边)能到达最远的祖先。如果 $mathrm{dfn}(u)$ 改为 $mathrm{low}(u)$,因为 $u$ 已经在 DFS 树中,那么 $mathrm{low}(v)$ 为经过两条后向边到达节点的 $mathrm{dfn}$,与定义不符。
对于根节点,如果它在 DFS 树上有两个或更多的子节点,则它为割点;
显然,根是两棵子树上节点的唯一连通方式,删掉后破坏原图的连通性。
对于一个 DFS 树上的非根节点 $u$,如果存在子节点 $v$,满足 $mathrm{low}(v) geq mathrm{dfn}(u)$,则 $u$ 为割点。
根据定义,$v$ 向上无法到达 $u$ 的父节点。如果 $mathrm{low}(v) = mathrm{dfn}(u)$ 即以 $v$ 为根的子树最远到达 $u$,删掉后子树下的点无法到达更远的祖先,故判读时可以取等。
模板
#include <cstdio>
const int SIZE = 100005;
int cut[SIZE], cutTot, root;
int low[SIZE], dfn[SIZE], time;
int h[SIZE], to[SIZE << 1], nxt[SIZE << 1], tot;
int min(int x, int y) {
return x < y ? x : y;
}
void add(int x, int y) {
to[++tot] = y;
nxt[tot] = h[x];
h[x] = tot;
}
void tarjan(int x) {
low[x] = dfn[x] = ++time;
int cnt = 0;
for (int i = h[x]; i; i = nxt[i]) {
int y = to[i];
if (!dfn[y]) {
cnt++;
tarjan(y);
low[x] = min(low[x], low[y]);
if (x == root && cnt >= 2) {
// 判断是否被计数过,下同
if (!cut[x]) cut[x] = ++cutTot;
} else if (x != root && dfn[x] <= low[y]) {
if (!cut[x]) cut[x] = ++cutTot;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
int main() {
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d %d", &x, &y);
add(x, y);
add(y, x);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) root = i, tarjan(i);
printf("%d
", cutTot);
for (int i = 1; i <= n; i++) if (cut[i]) printf("%d ",i);
return 0;
}