SCC强连通分量与缩点
\(SCC\)强连通分量与缩点
概念
定义:
强连通:若一个有向图 \(G\) 是强连通的,其含义是有向图 \(G\) 中任意两点 \(u\) 和 \(v\) 均相互可达
强连通分量:原图 \(G\) 中极大的强连通子图 \(G_{max}\) ,如下图所示
强连通分量如下:\(\{1, 2, 3, 4\}\), \(\{5\}\), \(\{6\}\), \(\{7, 9\}\), \(\{8\}\)
缩点:缩点即是将图中所有强连通分量浓缩成一个点,这个点代表整个强连通分量组成一个新的有向无环图(\(DAG\)),如下图所示:
算法实现
强连通分量
我们可以用解决双连通分量的方法解决强连通分量:
首先,考虑从一个点出发,建立 \(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 : 记录有效边(连接两个强连通分量的边)
例题
洛谷 P2746 [USACO5.3]校园网 Network of Schools
洛谷 P3627 [APIO2009] 抢掠计划 (不能使用 \(dijkstra\))

浙公网安备 33010602011771号