Tarjan 强连通分量学习笔记
鸽了好久
概念
强连通图:指的是一张有向图,其中从任意一个节点出发,都可达到该图的所有节点。
强连通分量:指的是有向图的一种子图,满足该子图为强连通图,且这个子图是 极大 的,即对于一幅图 \(G\) 的强连通分量 \(G_0\),我们无法找到一副子图 \(G_1\) 为强连通图,且满足 \(G_0 \subsetneqq G_1 \subseteq G\)
Tarjan
时间戳
对于一张图,我们可以使用 Tarjan 算法求出这张图的强连通分量。
Tarjan 算法引入了一个概念:时间戳,即对一张图进行 DFS 时经过每个点的顺序。用 \(\text{dfn}_i\) 表示。
对于下面这幅图:
该图对应的时间戳为:
\[ \begin{align} \text{dfn}_1 = 1 \quad \text{dfn}_2 = 2 \quad \text{dfn}_5 = 3 \quad \text{dfn}_4 = 4 \\ \text{dfn}_3 = 5 \quad \text{dfn}_7 = 6 \quad \text{dfn}_6 = 7 \end{align} \]
边的分类
我们可以很容易地看出,对于一个环,环中的节点都属于同一个强连通分量。
Tarjan 则是通过改编 DFS 来找“环”的一种算法。
于是我们可以基于时间戳将边分个类。首先我们定义搜索所经过的边构成了一颗搜索树(或森林)。
于是对于有向边 \((x, y)\) 我们可以得到下列几种边:
树枝边:即为搜索时经过的边,反映在搜索树上即为 \(x\) 为 \(y\) 的父节点;
前向边:即为在搜索树中祖先节点指向孙子节点,即 \(x\) 为 \(y\) 的祖先;
后向边:即为在搜索树中孙子节点指向祖先节点,即 \(y\) 为 \(x\) 的祖先;
横插边:即为除了上述三种情况的边,此时必满足 \(\text{dfn}_y < \text{dfn}_x\)。
下面这幅图很好地阐释了四种边的类型(摘自《算法竞赛进阶指南》)。加粗的边为树枝边,其余边则取第一个汉字:
求解强连通分量
对“环”的理解
对于“环”有影响的有两种边:后向边和横插边。
为了求后向边和横插边,我们可以维护一个栈,在访问 \(x\) 节点时,存储以下信息:
搜索树上 \(x\) 的祖先节点;
已经访问过的,且可通过一条路径就到达 \(x\) 祖先节点的节点。
对于节点 \(y\),只要 \(y\) 有一条路径到 \(x\) 的祖先,且存在一条 \(x \rightarrow y\) 的横插边,显然 \(x\)、\(x\) 的祖先和 \(y\) 形成了一个环,属于同一个强连通分量。
追溯值
接下来我们需要看怎么求树枝边和横插边。
这时我们可以引入一个追溯值 \(\text{low}_i\),表示由节点 \(i\) 开始搜索所能到达的点中,在搜索树上是 \(i\) 的祖先的节点中最小的时间戳。
于是我们可以对节点 \(x\) 进行下列操作:
当节点 \(x\) 第一次被访问时,把 \(x\) 入栈,初始化 \(\text{low}_x = \text{dfn}_x\);
扫描从 \(x\) 出发的边 \(x \rightarrow y\):
若 \(y\) 没访问过,则说明 \(y\) 是 \(x\) 的树枝边,递归访问 \(y\),从 \(y\) 回溯后,令 \(\text{low}_x = \min \{ \text{low}_x, \text{low}_y \}\);
若 \(y\) 被访问过并且 \(y\) 在栈中,则令 \(\text{low}_x = \min \{ \text{low}_x, \text{dfn}_y \}\)。
从 \(x\) 回溯之前,判断是否有 \(\text{low}_x = \text{dfn}_x\)。若成立,则不断从栈中弹出节点,直至 \(x\) 出栈。
显然在第三步中当 \(\text{low}_x = \text{dfn}_x\) 时,栈顶到栈中 \(x\) 之间必有节点都通过树枝边或横插边连向 \(x\),且其中的其余节点有至少一条路径连线该节点。即这些节点构成一个环,属于同一个强连通分量。此时只需在出栈的时候记录其中节点属于同一强连通分量即可。
Tarjan 的时间复杂度为 \(O(n + m)\),是一个非常高效的算法。
代码模板
1 | void tarjan(int x) { |
缩点
求解完强连通分量可选择将强连通分量缩成一个点以便计算。
代码模板见下:
1 | for (int i = 1; i <= n; i++) { |
例题
洛谷 P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G
本题仅需要将强连通分量缩点,统计出出度为 \(0\) 的点的个数。若个数为 \(1\),答案则为该连通分量内节点个数,否则没有明星牛。
1 |
|