云计算百科
云计算领域专业知识百科平台

UVa 11896 Inglorious Gangs

题目描述

在罪恶都市中,帮派以车队形式在街道上行驶。警察数量不足,只能由一名警察追捕一个帮派车队。当车队到达某个交叉路口时,每辆帮派车辆会分别驶向不同的方向,使落单的警察困惑,帮派从而逃脱。警方知道,帮派车队只有在能够于城市中某处重新集结的情况下才会分开。他们计划封锁部分道路,防止车队分开,从而让警察能一直追捕直到抓住他们。

城市地图是有向图,节点表示交叉路口,有向边表示道路。你需要判断在给定的未封锁道路下,是否已经能够困住帮派。如果能困住,输出 Trapped,否则输出 Not Trapped。

形式化地说:判断有向图中是否存在两个不同的顶点 uuuvvv,使得从 uuuvvv 存在两条边不相交的路径(即路径间没有共享的有向边,但可以共享顶点)。若存在,帮派可在 uuu 处分叉,并在 vvv 处重新集结,从而逃脱。

输入格式

第一行一个整数 TTTT≤100T \\le 100T100),表示测试用例数量。

每个测试用例以两个整数 NNNMMM 开头(1≤N≤50001 \\le N \\le 50001N5000M≤105M \\le 10^5M105),分别表示交叉路口的数量和未封锁道路的数量。

接下来 MMM 行,每行两个整数 uuu vvv1≤u,v≤N1 \\le u, v \\le N1u,vNu≠vu \\neq vu=v),表示存在一条从 uuuvvv 的有向道路。

保证没有自环,但可能存在平行边。

输出格式

对于每个测试用例,输出一行:若能够困住帮派,打印 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 有两条边分别到 222333,而 222 又能到达 333,帮派可在 111 分叉,在 333 汇合,存在边不相交路径。
  • 第二组:唯一的环是 2→3→4→22 \\to 3 \\to 4 \\to 22342,从 111 出发只有一条路径进入该环,无法分叉后重聚,因此困住。

题目分析

本题的本质是判断有向图中是否存在两条边不相交的路径。根据 Menger\\texttt{Menger}Menger 定理(边连通度版本),两点 uuuvvv 存在两条边不相交路径等价于它们之间的边连通度至少为 222,在网络流中表现为最大流不小于 222(每条边容量为 111)。但直接对每一对出度 ≥2\\ge 22 的节点跑最大流,复杂度高达 O(N⋅M⋅N)O(N \\cdot M \\cdot \\sqrt{N})O(NMN),无法承受 N≤5000N \\le 5000N5000 的规模。

我们需要更高效的方法。观察图的结构:

  • 强连通分量(SCC\\texttt{SCC}SCC)内部:若一个大小至少为 222SCC\\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 22SCC\\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 5000N5000bitset\\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}vdag[c]R[v]

第四步:DAG\\texttt{DAG}DAG 中判定

对于每个出度 ≥2\\ge 22SCC\\texttt{SCC}SCC ccc,依次检查它的所有出边邻居对应的 RRR

  • 维护一个 bitset\\texttt{bitset}bitset seen\\textit{seen}seen,表示已遍历过的邻居的可达集之并。
  • 若当前邻居的 RRRseen\\textit{seen}seen 有交集,则说明存在两条不同的出边,它们的可达集有重叠,从而存在边不相交路径。
  • 若有交集,直接判定可逃脱。

若所有检查均未发现,则输出 Trapped。

正确性证明

  • 内部判定:若 uuuSCC\\texttt{SCC}SCC 内部有至少两条出边,设指向 xxxyyy。由于 SCC\\texttt{SCC}SCC 强连通,存在 x⇝yx \\rightsquigarrow yxyy⇝uy \\rightsquigarrow uyu 等路径,结合 uuu 的出边,总能构造出两条边不相交的路径到达 SCC\\texttt{SCC}SCC 内某顶点(如 xxx 自身或经由环回到 uuu 等)。
  • SCC\\texttt{SCC}SCC 判定:设 ccc 有两条出边分别到 pppqqq,且 R[p]∩R[q]≠∅R[p] \\cap R[q] \\neq \\emptysetR[p]R[q]=。取交集中的一个 SCC\\texttt{SCC}SCC www,则从 pppwww 和从 qqqwww 的路径在 DAG\\texttt{DAG}DAG 中不会共享边(因为 p≠qp \\neq qp=q),且两条路径进入 www 的边必不同(否则它们会在更早的 SCC\\texttt{SCC}SCC 就汇合,与“最早交集”矛盾)。因此,原图中存在从某个 u∈cu \\in cuc 出发,经过不同边到达 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; }

总结

本题巧妙地将“边不相交路径”的判定转化为可达集交集的问题,避免了耗时的最大流计算。核心技巧在于:

  • 利用 Tarjan\\texttt{Tarjan}Tarjan 缩点将一般有向图转化为 DAG\\texttt{DAG}DAG,并在 SCC\\texttt{SCC}SCC 内部预先进行快速判定。
  • DAG\\texttt{DAG}DAG 上用 bitset\\texttt{bitset}bitset 压缩存储可达集合,使得交集判定可以在 O(N/64)O(N/64)O(N/64) 的位操作内完成,极大提升了效率。
  • 注意处理平行边和节点指向同一 SCC\\texttt{SCC}SCC 的特殊情况,确保不漏判。
  • 该解法充分利用了图的结构性质和位运算加速,是 SCC\\texttt{SCC}SCC 缩点与 bitset\\texttt{bitset}bitset 优化的经典应用。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » UVa 11896 Inglorious Gangs
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!