1 条题解

  • 0
    @ 2026-7-3 8:00:51

    题意

    给定一张 nn 个点 mm 条边的有向图 G=(V,E)G=(V,E),求一个导出子图 G=(V,E)G'=(V',E')(即先选定一个点集 VV',然后对于 i,jVi,j\in V',若 ijEi\to j\in E 那么有 ijEi\to j\in E'),使得对于 uVu\in V'uuGG' 中的入度和出度都恰好为一。输出一个方案或报告无解。

    1n10001\le n\le 10000m20000\le m\le 2000,保证图中无重边和自环。

    解法

    对于一个满足每个点入度出度均为 11 的图,显然它就是一个。不妨考虑这么构造:对于点 uu,先满足它的出度为 11,那么 uu 有且仅有一条出边连向某一个点。这条边必须连向另外一个点 vv,若 vv 还没有入边和出边,连边后继续考虑点 vv;若 vv 已经有了入边,那么连接后无法满足入度为 11,不可取;若 vv 没有入边但有一条出边,即 vv 是第一个考虑的点,那么连接 vv 后就完成了构图。这样构造出的图就是一个环。

    然后朴素的想法就是:在 GG 中选一个环出来输出就是答案。下面是一个反例:

    对于这张图,123411\to 2\to 3\to 4\to 1 即为一个环。但如果我们选取 V={1,2,3,4}V'=\{1,2,3,4\},那么导出子图就会包含 313\to 1 这条边,不满足子图中每个点入度、出度均为 11;而正确答案即为环长更小12311\to 2\to 3\to 1V={1,2,3,4}V'=\{1,2,3,4\} 不合法的原因就是出现了环套环。我们假设已经找到了原图中的所有环,那么对于环长最小的环显然不会出现环套环的情况,因为对于一个被套了的环环长一定更短。那么我们考虑找到环长最短的环即可。

    实现

    这里是一个找环的套路:考虑 dfs。我们维护一个栈,访问节点时将其入栈,回溯时将其出栈。对于点 uu 我们分别记录它是否被访问过是否在栈中(前者当 uu 出栈时仍然保留)。对于点 uu 和边 uvu\to v,如果 vv 已经在栈中,意味着我们已经找到了一个长成 vuvv\to \cdots \to u\to v 的环,中间省略的点即为从 vv 走到 uu 路径上压入栈里的节点。然后在所有环中找到环长最小的即可。初始时我们枚举 1n1\sim n​ 中还没被访问过的点作为起点。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    int n, m;
    const int maxm = 2005, maxn = 1005;
    namespace Graph {
        struct Edge { int to, nxt; } e[maxm];
        int head[maxn], ecnt = 0;
        void addEdge(int u, int v) {
            e[++ ecnt] = Edge { v, head[u] };
            head[u] = ecnt;
        }
    } using namespace Graph;
    int siz; vector<int> ans;
    int st[maxn], top; bool vis[maxn], used[maxn]; 
    void dfs(int u) {
        if (used[u]) {
            if (!vis[u]) return ;
            int now_siz = 1, i; 
            for (i = top; st[i] != u; i --, now_siz ++);
            if (siz <= now_siz) return ;
            siz = now_siz, ans.clear();
            for (; i <= top; i ++) ans.push_back(st[i]);
            return ;
        } vis[st[++ top] = u] = used[u] = 1;
        for (int i = head[u]; i; i = e[i].nxt) dfs(e[i].to);
        vis[st[top --]] = 0;
    }
    int main() {
        scanf("%d %d", &n, &m), siz = n + 1;
        for (int i = 1, u, v; i <= m; i ++)
            scanf("%d %d", &u, &v), addEdge(u, v);
        for (int i = 1; i <= n; i ++)
            if (!used[i]) dfs(i);
        if (siz == n + 1) return puts("-1"), 0;
        printf("%d\n", siz);
        for (auto x : ans) printf("%d\n", x);
        return 0;
    }
    
    • 1

    信息

    ID
    11750
    时间
    2000ms
    内存
    1024MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者