SCC强连通分量与缩点

\(SCC\)强连通分量与缩点

概念

定义:

强连通:若一个有向图 \(G\) 是强连通的,其含义是有向图 \(G\) 中任意两点 \(u\)\(v\) 均相互可达

强连通分量:原图 \(G\) 中极大的强连通子图 \(G_{max}\) ,如下图所示

graph LR; 1-->2 3-->1 2-->4 4-->3 4-->5 5-->8 5-->6 6-->7 6-->9 7<-->9

​ 强连通分量如下:\(\{1, 2, 3, 4\}\), \(\{5\}\), \(\{6\}\), \(\{7, 9\}\), \(\{8\}\)

缩点:缩点即是将图中所有强连通分量浓缩成一个点,这个点代表整个强连通分量组成一个新的有向无环图(\(DAG\)),如下图所示:

graph LR A((1,2,3,4)) B((5)) C((6)) D((8)) E((7,9)) A-->B B-->C B-->D C-->E

算法实现

强连通分量

我们可以用解决双连通分量的方法解决强连通分量:

首先,考虑从一个点出发,建立 \(dfs\) 树。我们定义在树上的边为树边。由于原图是一个图(有向图,这是与双连通分量的最大区别), 显然还会有一些边不是树边。下面我们具体讨论这些非树边(\(u\rightarrow v\))的情况

  • 返祖边:与双连通分量相同,返祖边即是从 \(dfs\) 树节点返回祖先的边

    这种情况下,\(u\)\(v\) 可以相互到达。此时 \(u\)\(v\) 从属于同一个强连通分量

  • 前向边:与返祖边相反,前向即是从 \(dfs\) 树节点前跳到子孙的边

    显然前向边无法保证 \(u\)\(v\) 相互可达。此时 \(u\)\(v\)属于同一个强连通分量(前向边与树边均指向下方,无法向上返回)

  • 横叉边:连接两个没有关系的点的边

    此时观察 \(v\)

    • 如果节点 \(v\) 能够返回 \(u\) 祖先,此时 \(u\)\(v\) 从属于同一个强连通分量
    • 反之,如果节点 \(v\) 不能够返回 \(u\) 祖先,此时 \(u\)\(v\)属于同一个强连通分量

综上,我们发现需要关注的就是能够沿着非树边(返祖边或横叉边)所能返回的最靠近根节点(最早访问,编号最小)。我们在 \(dfs\) 的时候用栈记录一个当前路径 \(stack_{ans}\)。如果当前节点 \(u\) 向下最多能够返回到自己,则该节点与它下面的节点属于同一个强连通分量。从 \(stack_{ans}\) 中比 \(u\) 早出栈的节点都属于 \(u\) 构成的强连通分量。

注意: 不是所有横叉边都能返回祖先,如果横叉边不在 \(stack_{ans}\) 中,那它已经被其它强连通分量包含,该横叉边无效

void dfs(int u) {
	dfn[u] = low[u] = ++ dfncnt; // 初值 到达本身
	sta.push(u); instack[u] = 1; // 入路径栈
	for (auto v : G[u]) {
		if (!dfn[v]) { // 树边 
			dfs(v);
			low[u] = min(low[u],low[v]); // 与孩子能够到达的最上端取最小值 下同
		} else if (instack[v]) low[u] = min(low[u],low[v]); // 非树边
	}
	if (dfn[u] == low[u]) { // 构成强连通分量
		scccnt ++; // 强连通分量编号
		while (sta.top() != u) { // 记录路径上的所有点
			scc[sta.top()] = scccnt;
			instack[sta.top()] = 0;
			sta.pop();
		}
		scc[u] = scccnt; instack[u] = 0; sta.pop(); // u 本身也要计算
	}
}
dfn[u] : u 节点在 dfs 树中的位置  dfncnt : 编号时间戳
low[u] : u 节点最多能够返回上方到哪个节点
sta : 路径栈
instack[u] : u 是否在栈 sta 中
scccnt : 强连通分量计数
scc[u] : u 点所属的强连通分量编号 scccnt
例题

洛谷 P2341 [USACO03FALL / HAOI2006] 受欢迎的牛 G

洛谷 P2863 [USACO06JAN]The Cow Prom S

缩点

我们将每一个点所属的强连通分量编号储存起来,如果一条边在强连通分量内部连接,则这条边无效,反之则连接。

void newGraph() {
	nn = scccnt;
	for (int u = 1;u <= n;u ++) {
		nval[scc[u]] += val[u];
		for (auto v : G[u]) {
			int nu = scc[u],nv = scc[v];
			if (nu != nv) st.insert(make_pair(nu,nv)); //不在同一强连通分量 
		}
	}
	for (auto edge : st) {
		int nu = edge.first,nv = edge.second;
		nG[nu].push_back(nv);
	}
}
nn : 新的点数
G : 原图
nG : 新图 
nval : 新点权(代表整个强连通分量中所有点)
st : 记录有效边(连接两个强连通分量的边)
例题

洛谷 P3387 【模板】缩点

洛谷 P2746 [USACO5.3]校园网 Network of Schools

洛谷 P3627 [APIO2009] 抢掠计划 (不能使用 \(dijkstra\)

posted @ 2026-08-27 07:20  Denia-kawaii  阅读(7)  评论(0)    收藏  举报