题目大意
给定一个 $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$ 中的最小环。
更进一步,可以先 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);