连通分量:对于一个有向图,一个分量中任意两点u, v,必然可以从u走到v,从v走到u
强连通分量:极大连通分量。
应用:
将有向图通过缩点变为有向无环图(DAG),即拓扑图。然后再做其它操作。
求解:
DFS的顺序,将所有边分为四大类
- 树枝边(
x -> y,x是y的父节点) - 前向边(
x -> y,x是y的祖先节点) - 横向边(
x -> y,y是之前搜过的其它分支上的节点) - 后向边(
x -> y,y是x的祖先节点)
判断当前节点x是否在某个强连通分量(SCC)中?
情况1:存在一条后向边指向x的祖先节点。
情况2:先走一条横向边,再走到x的祖先节点。
无论哪种情况,能成环的都是当前点的祖先节点。相当于一条后向边直至祖先节点。
如果当前点有一条横向边到达一个未在栈中的点,说明走到了死胡同。
Tarjan算法求强连通分量(SCC)
引入时间戳的概念(dfs过程中的自然数),对于每个点来说,记录两个时间戳dfn[u]表示遍历到u节点的时间戳low[u]表示从u开始走能遍历到的最小时间戳。
如果u是其所在的强连通分量的最高点,等价于dfn[u] == low[u]
缩点:
遍历每条边,找到两个端点所在的强连通分量,如果不是同一个SCC,说明这两个强连通分量之间有一条边。
Tarjan算法执行完后,按照强连通分量编号scc_count从大到小的顺序就是缩点后的图的拓扑序。
为什么呢?因为dfs求拓扑序(dfs遍历每一个点的所有临点,然后将当前点加入队列)的结果就是队列的逆序。而Tarjan添加每一个强连通分量就是按照dfs从小到大添加的,所以其逆序就是拓扑排序。
板子
import java.util.*;public class Main {static final int N = 10010, M = 50010;static int[] h = new int[N], e = new int[M], ne = new int[M];static int idx;static int[] dfn = new int[N], low = new int[N];static int time_cnt;static int[] stk = new int[N];static int p = -1;static boolean[] in_stk = new boolean[N];static int[] id = new int[N], size = new int[N];static int scc_cnt;static int n, m;static int[] dout = new int[N];public static void main(String[] args) {Scanner sc = new Scanner(System.in);n = sc.nextInt();m = sc.nextInt();Arrays.fill(h, -1);for (int i = 0; i < m; i++) {int a = sc.nextInt(), b = sc.nextInt();add(a, b);}for (int i = 1; i <= n; i++) {if (dfn[i] == 0) {tarjan(i);}}for (int i = 1; i <= n; i++) {for (int j = h[i]; j != -1; j = ne[j]) {int k = e[j];int a = id[i], b = id[k];if (a != b) {dout[a]++;}}}int cnt = 0, sum = 0;for (int i = 1; i <= scc_cnt; i++) {if (dout[i] == 0) {cnt++;sum += size[i];if (cnt > 1) {sum = 0;break;}}}System.out.println(sum);}static void tarjan(int u) {dfn[u] = low[u] = ++time_cnt;stk[++p] = u;in_stk[u] = true;for (int i = h[u]; i != -1; i = ne[i]) {int j = e[i];if (dfn[j] == 0){tarjan(j);low[u] = Math.min(low[u], low[j]);}else if (in_stk[j]) {low[u] = Math.min(low[u], dfn[j]);}}if (dfn[u] == low[u]) {int y;++scc_cnt;do {y = stk[p--];in_stk[y] = false;id[y] = scc_cnt;size[scc_cnt]++;} while (y != u);}}static void add(int a, int b) {e[idx] = b;ne[idx] = h[a];h[a] = idx++;}}
应用
AcWing 367. 学校网络
结论:缩点后的拓扑图至少加几条边才能变成一个强连通分量?
若本身就是一个强连通分量,加0条边就可以
若本身不是,统计入度为0的缩点的个数c1和出度为0的缩点的个数c2,需要加的边数等于max(c1, c2)
AcWing 1175. 最大半连通子图
强连通分量 + DP
AcWing 368. 银河
同糖果,之前用差分约束 + spfa
这次用强连通分量解决
tarjan缩点 -> 建缩点图 -> 拓扑序处理最长路 -> 遍历求和
