Tarjan算法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tarjan算法相关的知识,希望对你有一定的参考价值。

割点, 桥, 点双连通分量, 边双连通分量, 强连通分量

  割点, 桥, 点双连通分量, 边双连通分量, 强连通分量这些概念都是原图的一个诱导子图.

  割点, 桥, 双连通分量, 边双连通分量是无向图的相关概念, 而强连通分量是有向图的相关概念.

  $low$ 和 $dfn$ 的求法类似, $dfn[x]$ 表示点 $x$ 的时间戳, $low[x]$ 表示点 $x$ 通过后继能够追溯到的最早时间.

  1. 割点

  概念  删除这个点之后不连通.

  对 $u$ 为生成森林的根, $u$ 是割点, 当且仅当有超过 $1$ 个后继.

  对 $u$ 不是生成森林的根, $u$ 是割点, 当且仅当存在一个后继 $v$ , $low[v] \\ge dfn[u]$ .

  如果求路径 $(S, T)$ 的割点, 那么 $u$ 是割点, 当且仅当存在后继 $v$ , $T$ 在 $v$ 子树内, 且 $low[v] \\ge dfn[u]$ .

  2. 桥

  概念  删除这条边之后不连通.

  边 $(u, v)$ 是桥, 当且仅当 $low[v] > dfn[u]$ , 或者 $low[u] = dfn[v]$

  3. 点双连通分量

  概念  删除任意一个点后还连通.

  (1) 每条边只属于一个极大点双连通分量.

  (2) 每个极大点双连通分量中最多只有 $1$ 个割点, 且这个割点在搜索树的根.

  (3) 若某个点属于多个极大点双连通分量, 那么它是割点.

  $(2)(3) \\Rightarrow (4)$ 每个极大点双连通分量中最多只有一个点, 它属于极大点双连通分量, 且它是割点.

  在成功用后继 $v$ 判断一个点 $u$ 是割点的时候, 就能确定 $v$ 所在的极大点双连通分量.

  4. 边双联通分量

  概念  删除任意一条边后还连通.

  (1) 每个点只属于一个极大边双连通分量.

  (2) 每条边要么是桥, 要么属于一个极大边双连通分量.

  在成功判断桥 $(u, v)$ 的时候, 点 $v$ 下的若干条边组成了一个边双联通分量, 直接退栈并对进行染色.

  一条边 $(u, v)$ 是桥, 当且仅当 $u$ 和 $v$ 的染色不同.

  5. 强连通分量

  概念  删除任意一条边后还连通.

  与边双联通分量类似, 当 $low[u] = dfn[u]$ 时退栈.

  注意记录 $v[]$ 数组表示三种状态: 没访问, 访问过但没访问完, 访问完.

 

  对于有向图进行缩点, 新图的拓扑序怎么求?

  对于一张 DAG , 求它的拓扑序可以 DFS , 然后取结束时间顺序的逆序.

  Tarjan 算法对每个极大强连通分量标号顺序的逆序, 就是拓扑序.

 

  但是, 我们想再偷一个懒: 不将新图建出来, 用原图的信息来刻画新图的拓扑序.

  广义拓扑序:  对于有向图 G , 它的广义拓扑序满足: 对于一个强连通分量内的节点, 顺序任意; 对于两个不同强连通分量内的节点, 它们的强连通分量要满足拓扑序.

  发现求拓扑序的 DFS 算法可以直接推广到求广义拓扑序.

 

 

[GDOI 2015] [TH 1259] 水题

  题意

  给定一张图, 多组询问, 求删去某条边后, 不连通的点对数量.

  $n \\le 10 ^ 5, m \\le 10 ^ 6$ .

  分析

  用 Tarjan 求出所有的桥, 以及生成森林的每个子树的大小, 然后对删除的边, 瞎分类算一下.

 

 

[BZOJ 2208] 连通数

  题意

  给定一张 $n(n \\le 2000)$ 个点的有向图, 求有多少个有序点对 $(x, y)$ , 满足 $x$ 能够到达 $y$ .

  分析1  传递闭包 + bitset

  $f[i][j]$ 表示 $i$ 是否能够到达 $j$ , 那么可以利用 Floyd 求解传递闭包.

for (int k =1; k <= n; k++)
    for (int i = 1; i <= n; i++) if (f[i][k])
        for (int j = 1; j <= n; j++)
            f[i][j] |= f[k][j];

  发现可以利用 bitset 进行优化, 时间复杂度为 $O(\\frac{n ^ 3}{64})$

  分析2  Tarjan + bitset

  Tarjan 缩点, 然后在 DAG 上 DP 所能到达的后继信息, 所能到达的后继信息用 bitset 存储, 时间复杂度为 $O(\\frac{n ^ 3}{64})$ .

  实现上求出广义拓扑序后直接 DP 最简便.

    F(i, 1, n) {
        int cur = Lis[i];
        F(nx, 1, n) if (g[cur][nx] && col[cur] != col[nx])
            f[col[cur]] |= f[col[nx]];
    }

  小结  利用 Floyd 求解传递闭包的复杂度可以被优化到 $O(\\frac{n ^ 3}{64})$ .

 

 

 

[BZOJ 2788] Festival

 

  题意

  有 $X = \\left\\{ x_1, x_2, ..., x_n \\right\\}$ , 给定 $m_1 + m_2$ 个限制条件.

  有 $m_1$ 个限制条件 $a~b$ : $x_a + 1 = x_b$ .

  有 $m_2$ 个限制条件 $c~d$ : $x_c \\le x_d$ .

  求 $|\\cup_{k = 1} ^ n \\left\\{ x_k \\right\\}|$ 的最大值.

  $N \\le 600, m_1 + m_2 \\le 100000$ .

  分析

  差分约束系统等价建图.

  命题1  每个强联通分量可以独立算最大的贡献, 然后再相加.

  假如只满足强联通分量内部的情况, 那么对于一组解, 可以任意平移.

  对于缩点后的 DAG , 每个点的值都可以任意平移, 所以一定有一组解. 

 

  命题2  对于一个强联通分量, 设取得最小值的点为 $a$ , 取得最大值的点为 $b$ ,  强联通分量内的取值个数为 $x_b - x_a + 1$ .

  (1) 由于 $a$ 取得最小值, $b$ 取得最大值, 且值都为整数, 所以一定不超过 $x_b - x_a + 1$ .

  (2) 对于一个强联通分量, 点两两可互达, 所以 $a$ 可以到达 $b$ .

  注意到边权只有 $-1, 0, 1$, 所以 $d_v \\le d_u + 1$ . $a$ 要到达 $b$ , 必须经过值为 $x_a, x_{a+1}, x_{a+2}, ... x_b$ 的点.

  综上能取得 $x_a$ 到 $x_b$ 的所有值.

 

  我们枚举取得最小值的点 $a$ , 根据 定理2 , 我们要最大化最大值与当前点的差, 所以要以 $a$ 为源点跑单源最短路.

  如果 $a$ 真的能取得最小值, 那么就选择与 $a$ 距离最大的点 $b$ , $dist(b) + 1$ 就是答案.

 

  我们枚举每个点跑单源最短路, 等价于跑多源最短路, 用 Floyd .

  我们不需要检验了, 因为取得 $dist(b) + 1$ 的最大值对应的源点 $a$ , 一定能取得最小值.

 

 

[BZOJ 4727] [POI 2017] Turysta

  [题意]

  竞赛图:任意两个点之间有且仅有一条有向边的图。

  给定一张 $n(n \\le 2000)$ 个点的竞赛图,对每个点 $i$ ,输出以 $i$ 为起点的最长简单路径。

  [分析]

  先积累或分析竞赛图的性质,然后解决这个问题。

 

  定理1  竞赛图一定存在一条哈密顿路径。

  考虑利用归纳法进行推导。当只有 $1$ 个点的时候,显然;当 $n > 1$ 时,已经构造出了前 $n-1$ 个点的一条哈密顿路径 $a_1, a_2, ..., a_{n-1}$ ,尝试把第 $n$ 个点加入这条路径中。

  先考虑一些特殊情形:

  (1)$n$ 指向 $a_1$ ,那么可以直接把 $n$ 放在新的序列的第一位,否则 $a_1$ 指向 $n$ 。

  技术分享

  (2)若 $a_{n-1}$ 指向 $n$ ,那么可以直接把 $n$ 放在序列的最后一位。

  技术分享

  否则,$n$ 指向 $a_{n-1}$ , $1$ 指向 $n$ 。尝试找到 $a_i$ 指向 $n$ ,$n$ 指向 $a_{i+1}$ 。考虑从后往前找 $i$ ,那么假如之前不会停止,那么在 $i = 1$ 处一定会停止,所以一定能够找到这样一个 $i$ 。

  技术分享

 

  定理2  强连通的竞赛图一定存在一条哈密顿回路。

  根据定理1,可以构造出一个哈密顿路径 $a_1, a_2, ..., a_n$ 。$a_1$ 的回路就是本身,考虑每次由 $\\left\\{ a_1, a_2, ..., a_x \\right\\}$ 的哈密顿回路 $b_1, b_2, ..., b_x$ ,推出 $\\left\\{ a_1, a_2, ..., a_y \\right\\}, y > x$ 的哈密顿回路 $b_1, b_2, ..., b_y$ 。

  同样地,思路都是先处理一些特殊的情形,若不满足特殊的情形,那么相当于有了更多的信息,从而更好地求解。

  (1)若 $a_{x+1}$ 指向 $b_2$ ,那么直接将 $a_{x+1}$ 作为哈密顿回路的第一位,否则 $b_2$ 指向 $a_{x+1}$ 。

  技术分享

  (2)尝试找到 $2 \\le j \\le x-1$ ,满足 $b_j$ 指向 $a_{x+1}$ ,$a_{x+1}$ 指向 $b_{j+1}$ 。否则 $b_1, b_2, ..., b_x$ 指向 $a_{x+1}$ 。

  技术分享

  (3)由于图一定是强连通的,所以一定能够找到 $a_y$ 指向 $b_z$ ,那么也可以方便地构造:$a_y \\rightarrow b_z \\rightarrow b_{z+1} \\rightarrow ... \\rightarrow b_{z-1} \\rightarrow a_{x+1} \\rightarrow a_{x+2} \\rightarrow ... \\rightarrow a_{x-1}$ 。

  用代码实现构造过程的时候,注意要始终保持 $b_1 = a_x$ 。

 

  现在考虑这道题怎么做。

  一张竞赛图可以划分为若干个极大强连通分量。

  命题3  一个强连通分量内的任意一个点的后继都相同,且所有后继都可以直接到达。

  对于一个强连通分量内的点,一定全部走完,因为每个点的直接后继都是一样的,多选一些点肯定不亏。如果走到了强连通分量外,那么就选择一个能到达结点最多的直接后继。考虑对缩点之后的 DAG 进行 DP ,设 $f[i]$ 表示标号为 $i$ 的强连通分量中的点,能够走到多少个节点,对应存 $Nx[i]$ 表示走到哪个强连通分量中。

  [实现]

 1 #define G(u, v) (g[A[u]][A[v]])
 2 void Prework(int *A, int n) {
 3     F(i, 2, n) {
 4         if (G(i, 1)) {
 5             int tmp = A[i];
 6             D(j, i, 2) A[j] = A[j-1];
 7             A[1] = tmp;
 8         }
 9         else if (G(i-1, i)) continue;
10         else {
11             D(j, i-2, 1)
12                 if (G(j, i)) {
13                     int tmp = A[i];
14                     D(k, i, j+2) A[k] = A[k-1];
15                     A[j+1] = tmp;
16                     break;
17                 }
18         }
19     }
20     
21     static int t[N];
22     int Length;
23     F(i, 1, n-1)
24         if (i >= 3 && G(i+1, 2))
25             swap(A[1], A[i+1]);
26         else {
27             int done = 0;
28             F(j, 2, i-1)
29                 if (G(j, i+1) && G(i+1, j+1)) {
30                     Length = 0;
31                     t[++Length] = A[i+1];
32                     F(k, j+1, i) t[++Length] = A[k];
33                     F(k, 1, j) t[++Length] = A[k];
34                     F(k, 1, i+1) A[k] = t[k];
35                     done = 1; break;
36                 }
37             if (done) continue;
38             
39             F(j, i+2, n) {
40                 F(k, 1, i) if (G(j, k)) {
41                     Length = 0;
42                     t[++Length] = A[j];
43                     F(l, k, i) t[++Length] = A[l];
44                     F(l, 1, k-1) t[++Length] = A[l];
45                     F(l, i+1, j-1) t[++Length] = A[l];
46                     F(l, 1, j) A[l] = t[l];
47                     done = 1; break;
48                 }
49                 if (done) { i = j-1; break; }
50             }
51         }
52 }

 




以上是关于Tarjan算法的主要内容,如果未能解决你的问题,请参考以下文章

tarjan割点算法代码实现

HDU 1269 迷宫城堡 tarjan算法求强连通分量

Tarjan-LCA算法小记

关于tarjan

模板强连通分量和tarjan算法

有向图的强连通算法 -- tarjan算法