【图论好题】ABC #142 Task F Pure
题目大意
给定一个 \(N\) 个点 \(M\) 条边的有向图 \(G\),无重边、自环。找出图 \(G\) 的一个导出子图(induced subgraph) \(G'\),且 \(G'\) 中的每个点的入度和出度都是 1。
数据范围
- $ 1 \le N \le 1000$
- $ 0 \le M \le 2000$
分析
导出子图 \(G'\) 中的每个点的入度和出度都是 1 相当于说 \(G'\) 是一个环(cycle)。
若不考虑 \(G'\) 是导出子图这个条件,则可通过 DFS 判断图 \(G\) 中是否有环,若有,同时还可以找出一个环。
下面给出环的一个性质:
若有向图 \(C\) 是一个环,则往 \(C\) 中添一条边将产生一个更小的环。
读者自证不难。
因此若有向图 \(G\) 中有环,则 \(G\) 中的最小环必然是 \(G\) 的导出子图。
于是原问题可以转化成求图 \(G\) 中的最小环。
更进一步,可以先在图 \(G\) 上 DFS,找出一个环 \(C\),设 \(C\) 的点集是 \(V_C\);接着在由 \(V_C\) 导出的子图 \(G_C\) 上找一个最小环 \(C'\),则 \(C'\) 是 \(G_C\) 的导出子图,从而也是 \(G\) 的导出子图。
上述论证用到了导出子图的一个性质:
若 \(B\) 是 \(A\) 的导出子图,\(C\) 是 \(B\) 的导出子图,则 \(C\) 也是 \(A\) 的导出子图。
Implementation
下列代码是最暴力的实现。
int n, m; scan(n, m);
if (n == 1) {
println(-1);
return 0;
}
vv<int> g(n);
rep (m) {
int a, b; scan(a, b); --a, --b;
g[a].pb(b);
}
vb used(n);
vb vis(n);
int lim;
int root;
bool flag;
function<void(int,int)> dfs = [&](int u, int d) {
vis[u] = true;
if (d == lim) {
FOR (v, g[u]) {
if (v == root) {
flag = true;
println(d + 1);
break;
}
}
}
else {
FOR (v, g[u]) {
if (!vis[v] && !used[v]) {
dfs(v, d + 1);
if (flag) break; // 别忘了这里的 break
}
}
}
if (flag) println(u + 1);
};
for (lim = 1; lim < n; ++lim) {
fill(all(used), false);
rng (i, 0, n) {
fill(all(vis), false);
flag = false;
root = i;
dfs(i, 0);
if (flag) {
return 0;
}
used[i] = true;
}
}
println(-1);