Files
Data-Structure/模板//Tarjan强连通集合.cpp
2025-12-16 20:36:27 +08:00

130 lines
3.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bits/stdc++.h>
using namespace std;
/*
Tarjan 强连通分量 (SCC) 单次 DFS 实现0-indexed
- 输入图为邻接表 g (n nodes)
- 输出:
int scc_cnt : 强连通分量个数
vector<int> comp(n) : 每个节点所属的 SCC id0..scc_cnt-1
vector<vector<int>> groups: 若需要每个组件的节点列表(可选)
时间复杂度: O(n + m)
适用场景: 竞赛、图论题
*/
struct TarjanSCC {
int n;
vector<vector<int>> g;
// 维护项
vector<int> dfn; // dfn[v] = 发现时间从1开始0 表示未访问
vector<int> low; // low-link 值
vector<char> in_stack; // 是否在栈中
vector<int> stk; // 模拟栈(也可用 vector<int> 的 push_back/pop_back
int timer;
int scc_cnt;
vector<int> comp; // comp[v] = scc id (0..scc_cnt-1)
vector<vector<int>> groups; // 各 scc 的节点列表 (可选)
TarjanSCC(int n = 0) { init(n); }
void init(int n_) {
n = n_;
g.assign(n, {});
dfn.assign(n, 0);
low.assign(n, 0);
in_stack.assign(n, 0);
stk.clear();
timer = 0;
scc_cnt = 0;
comp.assign(n, -1);
groups.clear();
}
void add_edge(int u, int v) {
g[u].push_back(v);
}
// Tarjan DFS from vertex v
void dfs(int v) {
dfn[v] = low[v] = ++timer; // 发现时间
stk.push_back(v);
in_stack[v] = 1;
for (int to : g[v]) {
if (dfn[to] == 0) {
// tree-edge
dfs(to);
low[v] = min(low[v], low[to]);
} else if (in_stack[to]) {
// back-edge / cross-edge to a node still in current stack: can update low
low[v] = min(low[v], dfn[to]);
}
// 若 to 已被分派到某个 SCCin_stack[to]==0 且 dfn[to]>0则不能用来更新 low[v]
// 因为该节点已从栈中弹出并已归属某个 SCC与当前 v 的 SCC 不再相关)
}
// v 是 SCC 根:把栈中元素弹出直到 v组成一个 SCC
if (low[v] == dfn[v]) {
// 新的 SCC编号为 scc_cnt
groups.emplace_back();
while (true) {
int x = stk.back();
stk.pop_back();
in_stack[x] = 0;
comp[x] = scc_cnt;
groups.back().push_back(x);
if (x == v) break;
}
++scc_cnt;
}
}
// 运行 Tarjan返回 scc 数,并填充 comp/groups
int run() {
// reset timer & results in case run() 被多次调用
timer = 0;
scc_cnt = 0;
fill(dfn.begin(), dfn.end(), 0);
fill(low.begin(), low.end(), 0);
fill(in_stack.begin(), in_stack.end(), 0);
stk.clear();
comp.assign(n, -1);
groups.clear();
for (int v = 0; v < n; ++v) {
if (dfn[v] == 0) dfs(v);
}
return scc_cnt;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
TarjanSCC scc(n);
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
// 假设输入为 0-indexed若为 1-indexed请在这里做 --u; --v;
// --u; --v;
scc.add_edge(u, v);
}
int cnt = scc.run();
// 输出SCC 个数,和每个节点所属的 comp id
cout << "SCC count: " << cnt << '\n';
// comp id 范围 0..cnt-1Tarjan 的生成顺序:根被发现时分配 0,1,...
for (int v = 0; v < n; ++v) {
cout << scc.comp[v] << (v + 1 == n ? '\n' : ' ');
}
// 若想要缩点图的拓扑序:注意 Tarjan 分配的 id 不是缩点图的拓扑序(通常是逆拓扑或任意顺序)
// 若需要拓扑(从源到汇的顺序),可以构建缩点图并对其做拓扑排序。
return 0;
}