题目大意
给定一个 $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);