【图论好题】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);
posted @ 2019-09-29 02:47  Pat  阅读(322)  评论(0编辑  收藏  举报