給定有向圖,找出所有「底層」強連通分量 (SCC) 的頂點。底層 SCC 定義:該分量內無邊指向分量外部 (出度為 0)。輸出所有屬於底層 SCC 的頂點編號。
dfn[u]:DFS 訪問時間戳low[u]:u 及其子樹能回溯到的最早祖先時間戳low[u] == dfn[u] 時,彈出棧直到 u,形成一個 SCC#include <bits/stdc++.h>
using namespace std;
const int MAXV = 5005;
int n, m;
vector<int> G[MAXV];
stack<int> stk;
int dfn[MAXV], low[MAXV], dfsTime;
bool inStk[MAXV];
int compId[MAXV]; // 頂點 -> 分量 ID
int compCnt; // 分量計數
int outDeg[MAXV]; // 分量出度
void tarjan(int u) {
dfn[u] = low[u] = ++dfsTime;
stk.push(u);
inStk[u] = true;
for (int v : G[u]) {
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]); // 子樹回溯更新
} else if (inStk[v]) {
low[u] = min(low[u], dfn[v]); // 回邊/橫邊更新 (注意用 dfn[v] 而非 low[v])
}
}
if (low[u] == dfn[u]) { // 找到 SCC 根
++compCnt;
while (true) {
int v = stk.top(); stk.pop();
inStk[v] = false;
compId[v] = compCnt;
if (v == u) break;
}
}
}
void solveSCC() {
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(inStk, 0, sizeof(inStk));
compCnt = 0; dfsTime = 0;
while (!stk.empty()) stk.pop();
for (int i = 1; i <= n; ++i)
if (!dfn[i]) tarjan(i);
}
void countCompOutDeg() {
memset(outDeg, 0, sizeof(outDeg));
vector<bool> vis(n+1, false);
function<void(int)> dfs = [&](int u) {
vis[u] = true;
for (int v : G[u]) {
if (compId[u] != compId[v])
outDeg[compId[u]]++; // 跨分量邊
if (!vis[v]) dfs(v);
}
};
for (int i = 1; i <= n; ++i)
if (!vis[i]) dfs(i);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
while (cin >> n && n) {
cin >> m;
for (int i = 1; i <= n; ++i) G[i].clear();
while (!stk.empty()) stk.pop(); // 關鍵:多測試用例清空棧
for (int i = 0; i < m; ++i) {
int u, v; cin >> u >> v;
G[u].push_back(v);
}
solveSCC();
countCompOutDeg();
bool first = true;
for (int i = 1; i <= n; ++i) {
if (outDeg[compId[i]] == 0) {
if (!first) cout << ' ';
first = false;
cout << i;
}
}
cout << '\n';
}
return 0;
}
| 情況 | 更新 low[u] | 說明 |
|---|---|---|
| 樹邊 (v 未訪問) | low[u] = min(low[u], low[v]) | 子樹遞歸返回後,用子樹的最低點更新 |
| 回邊/橫邊 (v 在棧中) | low[u] = min(low[u], dfn[v]) | 直接連到祖先,**用 dfn[v] 而非 low[v]** |
| 橫邊 (v 不在棧中) | 不更新 | v 已屬於其他完整 SCC,不影響當前 |
dfn[v],樹邊用 low[v]