在一个有向图中,如果某两点间都有互相到达的路径,那么称中两个点强连通,如果任意两点都强连通,那么称这个图为强连通图;一个有向图的极大强连通子图(不被原图其它强连通子图包含)称为强连通分量。
Tarjan 算法可以在 $O(n + m)$ 的时间内求出一个图的所有强连通分量。
若将有向图的强连通分量都视作一个点,则原图会形成有向无环图(DAG)。
定义
栈,DFS 树中同一棵子树中的点在栈中是相邻的。
$mathrm{dfn}(u)$ 表示进入节点 $u$ 时的时间(时间截)。
$mathrm{low}(u)$ 表示由节点 $u$ 开始搜索所能到达的点中,在搜索树上是 $u$ 的祖先且 $mathrm{dfn}$ 最小的节点的 $mathrm{dfn}$。
描述
-
选定一个节点作为根节点,开始 DFS;
-
初始化当前点的 $mathrm{dfn}$ 和 $mathrm{low}$ 均为当前时间截,并进栈;
-
遍历当前点 $v$ 的所有邻接点( $v$ 出边连的点);
-
如果某个邻接点 $u$ 在栈中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;
-
如果某个邻接点 $u$ 不在栈中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;
-
$v$ 所有邻接点都完成 DFS 后,如果满足 $mathrm{low}(v) = mathrm{dfn}(v)$,则将栈中从 $v$ 到栈顶的所有元素出栈,并标记为一个强连通分量。
解释
如果某个邻接点 $u$ 在栈中,更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{dfn}(u))$;
$u$ 已被访问过且还未出栈,说明 $v$ 找到它的祖先 $u$,并形成了一个环,此时要用 $u$ 去更新 $v$ 的最远祖先。
如果某个邻接点 $u$ 不在栈中,则对 $u$ 进行 DFS,完成后更新 $mathrm{low}(v) = min(mathrm{low}(v), mathrm{low}(u))$;
点 $u$ 出发能到达的最远祖先,点 $v$ 一定也能到达。
$v$ 所有邻接点都完成 DFS 后,如果满足 $mathrm{low}(v) = mathrm{dfn}(v)$,则将栈中从 $v$ 到栈顶的所有元素出栈,并标记为一个强连通分量。
如果当前点 $v$ 为根的子树下,无论怎么走也无法到达 $v$ 的祖先,说明整棵子树下的点组成一个强连通分量。因为从 $v$ 进栈后,所有进栈的点都是子树内的点,所以这个点在栈中的位置到栈顶位置中的点为同一个强连通分量。
模板
#include <cstdio>
#include <cstring>
const int SIZE = 100005;
int scc[SIZE], sccTot;
int st[SIZE], top;
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;
st[++top] = x;
for (int i = h[x]; i; i = nxt[i]) {
int y = to[i];
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
} else if (!scc[y]) {
low[x] = min(low[x], dfn[y]);
}
}
if (low[x] == dfn[x]) {
scc[x] = ++sccTot;
while (st[top] != x) {
scc[st[top--]] = sccTot;
}
top--;
// x 出栈
}
}
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);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
printf("%d
", sccTot);
for (int i = 1; i <= n; i++) printf("%d ", scc[i]);
return 0;
}