题目描述
在罪恶都市中,帮派以车队形式在街道上行驶。警察数量不足,只能由一名警察追捕一个帮派车队。当车队到达某个交叉路口时,每辆帮派车辆会分别驶向不同的方向,使落单的警察困惑,帮派从而逃脱。警方知道,帮派车队只有在能够于城市中某处重新集结的情况下才会分开。他们计划封锁部分道路,防止车队分开,从而让警察能一直追捕直到抓住他们。
城市地图是有向图,节点表示交叉路口,有向边表示道路。你需要判断在给定的未封锁道路下,是否已经能够困住帮派。如果能困住,输出 Trapped,否则输出 Not Trapped。
形式化地说:判断有向图中是否存在两个不同的顶点 uuu 和 vvv,使得从 uuu 到 vvv 存在两条边不相交的路径(即路径间没有共享的有向边,但可以共享顶点)。若存在,帮派可在 uuu 处分叉,并在 vvv 处重新集结,从而逃脱。
输入格式
第一行一个整数 TTT(T≤100T \\le 100T≤100),表示测试用例数量。
每个测试用例以两个整数 NNN 和 MMM 开头(1≤N≤50001 \\le N \\le 50001≤N≤5000,M≤105M \\le 10^5M≤105),分别表示交叉路口的数量和未封锁道路的数量。
接下来 MMM 行,每行两个整数 uuu vvv(1≤u,v≤N1 \\le u, v \\le N1≤u,v≤N,u≠vu \\neq vu=v),表示存在一条从 uuu 到 vvv 的有向道路。
保证没有自环,但可能存在平行边。
输出格式
对于每个测试用例,输出一行:若能够困住帮派,打印 Trapped;否则打印 Not Trapped。
样例
输入
2
3 3
1 2
2 3
1 3
4 4
1 2
2 3
3 4
4 2
输出
Not Trapped
Trapped
解释:
- 第一组:从 111 有两条边分别到 222 和 333,而 222 又能到达 333,帮派可在 111 分叉,在 333 汇合,存在边不相交路径。
- 第二组:唯一的环是 2→3→4→22 \\to 3 \\to 4 \\to 22→3→4→2,从 111 出发只有一条路径进入该环,无法分叉后重聚,因此困住。
题目分析
本题的本质是判断有向图中是否存在两条边不相交的路径。根据 Menger\\texttt{Menger}Menger 定理(边连通度版本),两点 uuu 到 vvv 存在两条边不相交路径等价于它们之间的边连通度至少为 222,在网络流中表现为最大流不小于 222(每条边容量为 111)。但直接对每一对出度 ≥2\\ge 2≥2 的节点跑最大流,复杂度高达 O(N⋅M⋅N)O(N \\cdot M \\cdot \\sqrt{N})O(N⋅M⋅N),无法承受 N≤5000N \\le 5000N≤5000 的规模。
我们需要更高效的方法。观察图的结构:
- 强连通分量(SCC\\texttt{SCC}SCC)内部:若一个大小至少为 222 的 SCC\\texttt{SCC}SCC 中某个顶点有两条边指向同一 SCC\\texttt{SCC}SCC 内的其他顶点,则由于强连通性,这两条边出发的路径必然能在该 SCC\\texttt{SCC}SCC 内某处汇合,从而形成边不相交路径。
- 跨 SCC\\texttt{SCC}SCC:缩点后得到有向无环图(DAG\\texttt{DAG}DAG)。在 DAG\\texttt{DAG}DAG 中,若一个 SCC\\texttt{SCC}SCC 的出度至少为 222,且存在两条不同的出边指向的 SCC\\texttt{SCC}SCC,它们的“可达 SCC\\texttt{SCC}SCC 集合”有交集,则最早的交汇 SCC\\texttt{SCC}SCC 处一定存在边不相交的路径(因为不同分支首次汇合时,进入该 SCC\\texttt{SCC}SCC 的边必然不同)。
关键转化:我们不需要精确找到汇合顶点,只需在 DAG\\texttt{DAG}DAG 中检查可达集的交集即可。
解题思路
第一步:强连通分量缩点
使用 Tarjan\\texttt{Tarjan}Tarjan 算法求出所有 SCC\\texttt{SCC}SCC,并记录每个顶点所属的 SCC\\texttt{SCC}SCC 编号 cuc_ucu,以及每个 SCC\\texttt{SCC}SCC 的大小 sz[c]sz[c]sz[c]。
统计每个顶点 uuu 在原图中,连向同一 SCC\\texttt{SCC}SCC 的出边数量 innerOut[u]\\textit{innerOut}[u]innerOut[u]。
第二步:SCC\\texttt{SCC}SCC 内部快速判定
遍历每个大小 ≥2\\ge 2≥2 的 SCC\\texttt{SCC}SCC,若其中存在某个顶点 uuu 满足 innerOut[u]≥2\\textit{innerOut}[u] \\ge 2innerOut[u]≥2,则直接判定存在边不相交路径(帮派可逃脱)。因为 uuu 有两条边留在 SCC\\texttt{SCC}SCC 内部,强连通性保证这两条路径可以汇合。
第三步:构建 DAG\\texttt{DAG}DAG 并计算可达集
忽略 SCC\\texttt{SCC}SCC 内部的边,将每个 SCC\\texttt{SCC}SCC 视为一个超级节点,建立 DAG\\texttt{DAG}DAG。同时统计每个 SCC\\texttt{SCC}SCC 的出度 dout[c]d_{\\text{out}}[c]dout[c]。
对 DAG\\texttt{DAG}DAG 进行拓扑排序,按拓扑逆序计算每个 SCC\\texttt{SCC}SCC 的可达集合 R[c]R[c]R[c],用一个 bitset\\texttt{bitset}bitset 存储(因为 N≤5000N \\le 5000N≤5000,bitset\\texttt{bitset}bitset 仅需约 625625625 字节,快速且空间充足)。
R[c]={c}∪⋃v∈dag[c]R[v]R[c] = \\{c\\} \\cup \\bigcup_{v \\in \\text{dag}[c]} R[v]R[c]={c}∪v∈dag[c]⋃R[v]
第四步:DAG\\texttt{DAG}DAG 中判定
对于每个出度 ≥2\\ge 2≥2 的 SCC\\texttt{SCC}SCC ccc,依次检查它的所有出边邻居对应的 RRR:
- 维护一个 bitset\\texttt{bitset}bitset seen\\textit{seen}seen,表示已遍历过的邻居的可达集之并。
- 若当前邻居的 RRR 与 seen\\textit{seen}seen 有交集,则说明存在两条不同的出边,它们的可达集有重叠,从而存在边不相交路径。
- 若有交集,直接判定可逃脱。
若所有检查均未发现,则输出 Trapped。
正确性证明
- 内部判定:若 uuu 在 SCC\\texttt{SCC}SCC 内部有至少两条出边,设指向 xxx 和 yyy。由于 SCC\\texttt{SCC}SCC 强连通,存在 x⇝yx \\rightsquigarrow yx⇝y 或 y⇝uy \\rightsquigarrow uy⇝u 等路径,结合 uuu 的出边,总能构造出两条边不相交的路径到达 SCC\\texttt{SCC}SCC 内某顶点(如 xxx 自身或经由环回到 uuu 等)。
- 跨 SCC\\texttt{SCC}SCC 判定:设 ccc 有两条出边分别到 ppp 和 qqq,且 R[p]∩R[q]≠∅R[p] \\cap R[q] \\neq \\emptysetR[p]∩R[q]=∅。取交集中的一个 SCC\\texttt{SCC}SCC www,则从 ppp 到 www 和从 qqq 到 www 的路径在 DAG\\texttt{DAG}DAG 中不会共享边(因为 p≠qp \\neq qp=q),且两条路径进入 www 的边必不同(否则它们会在更早的 SCC\\texttt{SCC}SCC 就汇合,与“最早交集”矛盾)。因此,原图中存在从某个 u∈cu \\in cu∈c 出发,经过不同边到达 www 中某顶点的两条边不相交路径。
复杂度分析
- Tarjan\\texttt{Tarjan}Tarjan 缩点:O(N+M)O(N + M)O(N+M)
- 构建 DAG\\texttt{DAG}DAG 与拓扑排序:O(N+M)O(N + M)O(N+M)
- 计算可达集:共 O(N)O(N)O(N) 个 bitset\\texttt{bitset}bitset,每个大小为 N/64N/64N/64,合并在拓扑序下总时间为 O(N⋅(N/64))≈4×105O(N \\cdot (N/64)) \\approx 4 \\times 10^5O(N⋅(N/64))≈4×105 次位运算,非常快。
- 交集判定:每个 SCC\\texttt{SCC}SCC 最多遍历其出边,总位运算次数 O(M⋅(N/64))O(M \\cdot (N/64))O(M⋅(N/64)) 也在合理范围内。
- 总时间复杂度:O(N2/64+M)O(N^2/64 + M)O(N2/64+M),空间复杂度:O(N2/64+M)O(N^2/64 + M)O(N2/64+M),均可通过。
代码实现
// Inglorious Gangs
// UVa ID: 11896
// Verdict: Accepted
// Submission Date: 2026-06-22
// UVa Run Time: 0.370s
//
// 版权所有(C)2026,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
void solve() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int T; cin >> T;
while (T—) {
int N, M; cin >> N >> M;
vector<vector<int>> g(N + 1);
vector<int> od(N + 1), id(N + 1);
bool parallel = false;
unordered_set<long long> E;
for (int i = 0; i < M; ++i) {
int u, v; cin >> u >> v;
g[u].push_back(v);
od[u]++; id[v]++;
long long key = 1LL * u * (N + 1) + v;
if (!E.insert(key).second) parallel = true;
}
if (parallel) { cout << "Not Trapped\\n"; continue; }
// Tarjan算法缩点
vector<int> dfn(N + 1), low(N + 1), comp(N + 1);
vector<bool> inSt(N + 1);
stack<int> st;
int tm = 0, sc = 0;
function<void(int)> dfs = [&](int u) {
dfn[u] = low[u] = ++tm;
st.push(u); inSt[u] = true;
for (int v : g[u]) {
if (!dfn[v]) {
dfs(v);
low[u] = min(low[u], low[v]);
} else if (inSt[v]) low[u] = min(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++sc;
int v;
do {
v = st.top(); st.pop();
inSt[v] = false;
comp[v] = sc;
} while (v != u);
}
};
for (int i = 1; i <= N; ++i) if (!dfn[i]) dfs(i);
// SCC 内部出度与大小
vector<int> innerOut(N + 1), sz(sc + 1);
for (int i = 1; i <= N; ++i) sz[comp[i]]++;
for (int u = 1; u <= N; ++u)
for (int v : g[u])
if (comp[u] == comp[v]) innerOut[u]++;
bool escaped = false;
for (int c = 1; c <= sc && !escaped; ++c) {
if (sz[c] < 2) continue;
for (int u = 1; u <= N; ++u)
if (comp[u] == c && innerOut[u] >= 2) { escaped = true; break; }
}
if (escaped) { cout << "Not Trapped\\n"; continue; }
// 检查原图中节点出度>=2且两条边进入同一个大小>=2的SCC
for (int u = 1; u <= N && !escaped; ++u) {
if (od[u] < 2) continue;
unordered_set<int> seenSCC;
for (int v : g[u]) {
int c = comp[v];
if (sz[c] >= 2 && !seenSCC.insert(c).second) { escaped = true; break; }
}
}
if (escaped) { cout << "Not Trapped\\n"; continue; }
// 缩点 DAG
vector<vector<int>> dag(sc + 1);
vector<int> dout(sc + 1), din(sc + 1);
vector<unordered_set<int>> tmp(sc + 1);
for (int u = 1; u <= N; ++u)
for (int v : g[u])
if (comp[u] != comp[v])
if (tmp[comp[u]].insert(comp[v]).second) {
dag[comp[u]].push_back(comp[v]);
dout[comp[u]]++;
din[comp[v]]++;
}
// 拓扑排序
vector<int> tp;
queue<int> q;
for (int i = 1; i <= sc; ++i) if (din[i] == 0) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop();
tp.push_back(u);
for (int v : dag[u]) if (—din[v] == 0) q.push(v);
}
// 计算每个SCC的可达集
vector<bitset<5005>> R(sc + 1);
for (auto it = tp.rbegin(); it != tp.rend(); ++it) {
int u = *it;
R[u].set(u);
for (int v : dag[u]) R[u] |= R[v];
}
// DAG中出度>=2的SCC检查可达集交集
for (int c = 1; c <= sc && !escaped; ++c) {
if (dout[c] < 2) continue;
bitset<5005> seen;
for (int v : dag[c]) {
if ((seen & R[v]).any()) { escaped = true; break; }
seen |= R[v];
}
}
cout << (escaped ? "Not Trapped" : "Trapped") << '\\n';
}
}
int main() { solve(); return 0; }
总结
本题巧妙地将“边不相交路径”的判定转化为可达集交集的问题,避免了耗时的最大流计算。核心技巧在于:
该解法充分利用了图的结构性质和位运算加速,是 SCC\\texttt{SCC}SCC 缩点与 bitset\\texttt{bitset}bitset 优化的经典应用。
网硕互联帮助中心




评论前必须登录!
注册