Tarjan 割点学习笔记

在一个无向图中,如果删掉点 $v$ 后图的连通块数量增加,则称点 $v$ 为图的割点

Tarjan 算法可以在线性时间求无向图的所有割点。

定义

$mathrm{dfn}(u)$ 表示进入节点 $u$ 时的时间(时间截)。

$mathrm{low}(u)$ 表示由节点 $u$ 开始搜索所能到达的点中,在搜索树上是 $u$ 的祖先且 $mathrm{dfn}$ 最小的节点的 $mathrm{dfn}$。

描述

类似于 Tarjan 求强连通分量,大致流程:

  1. 选定一个节点作为根节点,开始 DFS;

  2. 初始化当前点的 $mathrm{dfn}$ 和 $mathrm{low}$ 均为当前时间截;

  3. 遍历当前点 $v$ 的所有邻接点(与 $v$ 相连的点);

  4. 如果某个邻接点 $u$ 在 DFS 树中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;

  5. 如果某个邻接点 $u$ 不在 DFS 树中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;

  6. 对于根节点,如果它在 DFS 树上有两个或更多的子节点,则它为割点;

  7. 对于一个 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$,删掉后子树下的点无法到达更远的祖先,故判读时可以取等。

模板

Luogu 3388

#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;
}

参考资料

原文地址:https://www.cnblogs.com/lcfsih/p/14391388.html