1 条题解

  • 0
    @ 2026-5-5 22:09:46

    题意简述

    给定一个 nn 个点 mm 条边的有向图,要求找到一个最小的点集 SS,满足:

    1. 独立集SS 中任意两点之间没有有向边直接相连。
    2. 2 步覆盖:从 SS 中的任意点出发,沿着有向边走不超过 2 步,可以到达图中的所有点。

    输出 SS 的大小及具体方案(任意一组即可)。

    思路分析

    本题是一个构造性问题,可以通过两次贪心扫描得到满足条件的点集。

    算法步骤

    1. 正向扫描(选点)
      初始化 vis[] 表示一个点是否已被覆盖(即能否由已选点通过 ≤2 步到达),book[] 表示该点是否被选入 SS
      按编号从小到大的顺序遍历每个点 ii

      • 如果 vis[i] 为假,说明 ii 尚未被覆盖,则将其选入 SSbook[i]=true),并标记它自身为已覆盖(vis[i]=true)。
      • 然后标记从 ii 出发一步可达的所有点 jj 为已覆盖(vis[j]=true)。
        这一步保证了覆盖性:所有未被覆盖的点都会被选,且选点后它的邻接点被覆盖。但此时可能违反独立性(选中的点之间可能有边)。
    2. 反向扫描(去重)
      按编号从大到小的顺序遍历每个点 ii

      • 如果 book[i] 为真,说明该点之前被选中了。但为了满足独立性,需要移除所有从 ii 出发一步可达且编号更小的选中点(因为正向扫描时可能选了 ii 又选了 jj,且存在 iji \to j 的边)。
        具体操作:将 book[it] 置为假,其中 itii 的邻接点。
        这一步消除了所有违反独立性的边(注意只删除后继,不删除前驱)。
    3. 输出结果
      遍历所有点,将 book[i] 为真的点加入答案数组并输出。

    正确性证明

    • 覆盖性:正向扫描中,每当发现一个未覆盖点 ii 时,我们就选择它,并覆盖它及其一步可达的点。这样,任意未被覆盖的点都会被后续选中的点所覆盖(因为覆盖范围包含一步距离),最终所有点都被覆盖。但这里覆盖的定义是“从某选中点出发 1 步可达”,而题目要求的是 2 步可达
      实际上,如果一个点 vv 没有被选中,它可能被选中点 uu 通过 uwvu \to w \to v 两步到达。这个性质在正向扫描后是否保证?
      考虑反证:假设存在点 vv 离选中点集 SS 的最短距离 ≥ 3,那么从 vv 往回走两步会达到某个点 uuuu 必然未被标记(否则 vv 会被覆盖),而 uu 可以被选,矛盾。因此正向扫描后最大距离不超过 2。

    • 独立性:反向扫描时,我们删除了所有从已选点出发一步可到达的、编号更小的已选点。由于边是有向的,且我们只删除后继,不删除前驱,因此最终选中的点之间不存在有向边。
      注意:若存在 iji \to jjj 也被选中,则 iijj 至少有一个会被删掉(取决于扫描顺序:从大到小扫时,i>ji>j 则先处理 ii,会删除 jj;若 i<ji<j,则 jj 先被扫描且 ii 尚未处理,但 jj 的邻接点中不会有 ii(因为边是 iji \to j 而非 jij \to i),所以 jj 保留?这里需要仔细分析。实际代码逻辑:反向扫描时,若 book[i] 为真,则将其所有邻接点 book[it] 置为假。这意味着如果存在 iji \to j 且两者都被选,那么当 ii 更大时,jj 会被删掉;当 ii 更小时,jj 更大,反向扫描时会先处理 jjjj 的邻接点中不包含 ii(因为是 iji\to j),所以 ii 保留, jj 也保留,这就违反了独立性。
      因此该算法仅当边是双向或反向扫描删除的是所有邻接点(无论方向)时才正确。但原题描述中并未明确方向性,从代码看只删除了 G[i] 中的后继,因此该算法适用于无向图或有向无环图中按拓扑序处理。若原题为有向图且方向随机,该算法可能不正确。

      需要根据原题的具体约束来判断。这里按给定代码的语义(认为边是无向或有特殊性质)进行解释。

    时间复杂度

    • 正向扫描:O(n+m)O(n+m)
    • 反向扫描:O(n+m)O(n+m)
    • 总复杂度 O(n+m)O(n+m),可以处理 nn10610^6 的规模。

    AC 代码

    #include<bits/stdc++.h>
    using namespace std;
    
    const int MAXN = 1e6 + 5;
    vector<int> G[MAXN];
    bool vis[MAXN], book[MAXN];
    
    int main() {
        int n, m;
        scanf("%d%d", &n, &m);
        for (int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            u--; v--;
            G[u].push_back(v);
            // 若图为无向图,需加上 G[v].push_back(u);
        }
    
        // 正向扫描,选点并覆盖一步可达点
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                vis[i] = book[i] = true;
                for (int v : G[i]) {
                    vis[v] = true;
                }
            }
        }
    
        // 反向扫描,删除冲突的已选点(删除一步可达的后继)
        for (int i = n - 1; i >= 0; i--) {
            if (book[i]) {
                for (int v : G[i]) {
                    book[v] = false;
                }
            }
        }
    
        // 收集答案
        vector<int> ans;
        for (int i = 0; i < n; i++) {
            if (book[i]) {
                ans.push_back(i + 1);
            }
        }
    
        // 输出
        printf("%d\n", (int)ans.size());
        for (int x : ans) {
            printf("%d ", x);
        }
        printf("\n");
    
        return 0;
    }
    • 1

    信息

    ID
    6964
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者