Luogu P13564「CZOI-R5」奶龙

题目分析

本题要求我们构造一个 \(n\) 个点的有向图,满足以下几个核心条件:

  1. 图的结构:每个点的出度都恰好为 \(1\),且图中没有自环。

    • 一个所有点出度均为 \(1\) 的有向图,通常被称为功能图 (functional graph)。在这种图中,每个节点 \(u\) 都有一个唯一的后继节点 \(v\)(即边 \(u \to v\))。
    • 从任意一个点出发,沿着边一直走,最终必然会进入一个。整个图的结构就是由若干个互不相连的组件构成,每个组件都是一个环,以及若干个以环上节点为根的、方向指向环的树。
    • “不得有自环”意味着环的长度至少为 \(2\)
  2. 奶龙的移动:奶龙从给定的 \(m\) 个起始点 \(A_i\) 出发,每次行动沿着有向边移动一步。

  3. 覆盖条件

    • 恰好 \(k\) 次行动后:所有 \(n\) 个点都被至少一只奶龙访问过。
    • 恰好 \(k-1\) 次行动后:至少有一个点未被任何奶龙访问过。

这两个覆盖条件是本题的关键。它们共同描述了一个“临界状态”:在第 \(k\) 次行动时,恰好访问到了全图中最后几个未被访问过的点。

\(V_t\) 表示所有奶龙经过 \(t\) 次行动后访问过的节点的集合(包含起始点,即 \(t=0\) 的情况)。题目要求我们构造图,使得:$ |V_{k-1}| < n $ 且 $ |V_k| = n $。

核心思路与构造策略

面对一个构造题,我们通常希望将复杂的、可能互相交错的情况(如此处多个奶龙的路径可能重叠)转化为一个简单、清晰、易于分析的模型。

一个自然的想法是,让不同奶龙的路径尽可能不要重叠,以便我们能清晰地计算出被访问节点的数量。我们可以设计一个“中心辐射”或“多链汇合”式的结构。

具体策略如下:

  1. 选定一个“中心点”:我们从 \(m\) 个奶龙的起始点中任选一个,比如 \(A_1\),作为所有路径最终的汇合点。

  2. 为每只奶龙构建路径:对于每只奶龙(包括从 \(A_1\) 出发的那只),我们都尝试为它构建一条由全新节点组成的、长度为 \(k\) 的路径。这里的“全新节点”指的是除 \(m\) 个起始点 \(A_i\) 之外的 \(n-m\) 个点,我们称它们为“自由节点”。

  3. 路径连接

    • 对于从 \(A_i\) 出发的奶龙,我们构建路径 \(A_i \to v_{i,1} \to v_{i,2} \to \dots \to v_{i,k}\),其中 \(v_{i,j}\) 都是自由节点。
    • 为了满足“所有点出度为 1”的条件,路径的终点 \(v_{i,k}\) 也需要一条出边。我们将它连接到我们选定的中心点 \(A_1\)。这样,所有路径最终都会汇入一个以 \(A_1\) 为一部分的结构中。
    • 通过这种方式,从 \(A_1\) 出发的路径 \(A_1 \to v_{1,1} \to \dots \to v_{1,k}\) 的终点 \(v_{1,k}\) 也连接回 \(A_1\),形成了一个长度为 \(k+1\) 的环。其他所有路径最终都会进入这个主环。
  4. 处理所有节点:我们需要使用所有的 \(n-m\) 个自由节点来构建这些路径。如果 \(n-m\) 个自由节点不足以给每只奶龙都分配 \(k\) 个新节点,我们就尽力分配。如果分配完所有自由节点后,某些奶龙的路径长度不足 \(k\),这也是允许的。只要我们能保证至少有一条路径的长度恰好为 \(k\),就能满足题目的临界条件。

这种构造方法的好处是:

  • 结构清晰:图的形态是若干条链,全部汇入一个主环。
  • 满足出度为 1:每条链上的中间节点,出度和入度都为1。链的起点(\(A_i\))和终点(连接到 \(A_1\))也都有了出边。所有未被用到的自由节点(如果有的话)也可以直接连向 \(A_1\),以满足出度为1的条件。(虽然在最终的代码实现中,所有自由节点都会被用上)。
  • 满足访问条件:只要我们能构造出一条长度为 \(k\) 的“新节点”链,那么这条链的第 \(k\) 个节点就一定是在第 \(k\) 步才首次被访问。这就满足了 \(|V_{k-1}| < n\) 的条件。同时,只要我们构造的路径覆盖了所有 \(n\) 个节点,\(|V_k|=n\) 的条件也能被满足。

无解情况分析

在尝试构造之前,我们需要判断在哪些情况下是无解的。

无解条件 1:n > m * (k + 1)

  • 含义:节点总数 \(n\) 大于 \(m\) 只奶龙在 \(k\) 步内所能访问到的最大节点数。
  • 分析
    • 一只奶龙,从它的起始点出发,经过 \(k\) 次行动,最多能访问 \(k+1\) 个不同的节点(即它自己和路径上的 \(k\) 个新节点)。
    • \(m\) 只奶龙,即使它们的路径完全不重叠(这是最理想的情况),在 \(k\) 步内总共最多能访问 \(m \times (k+1)\) 个节点。
    • 如果总节点数 \(n\) 超过了这个理论上限,那么无论如何构造图,都不可能在 \(k\) 步内访问到所有节点。
    • 因此,若 \(n > m(k+1)\),则必定无解。

无解条件 2:n - m < k

  • 含义:“自由节点”的数量少于 \(k\)
  • 分析
    • 题目的核心要求是,在第 \(k\)恰好完成了全图的覆盖,这意味着必须存在某个节点 \(u\),它在第 \(k-1\) 步及以前从未被访问,而在第 \(k\) 步被首次访问。
    • 要实现这一点,必须存在一条路径,形如 \(A_i \to v_1 \to v_2 \to \dots \to v_{k-1} \to u\)。为了保证 \(u\) 是在第 \(k\)首次被访问,这条路径上的 \(k+1\) 个点(\(A_i, v_1, \dots, v_{k-1}, u\))必须是独特的。更具体地说,路径上的 \(v_1, \dots, v_{k-1}, u\)\(k\) 个节点必须是“新”的,即它们不能是任何奶龙的起始点 \(A_j\)
    • 这些“新”节点只能从 \(n-m\) 个自由节点中选取。
    • 因此,为了能构造出这样一条长度为 \(k\) 的“新”路径,我们至少需要 \(k\) 个自由节点。
    • 如果自由节点的数量 \(n-m\) 小于 \(k\),那么任何一只奶龙从起点出发,走不到 \(k\) 步就会用尽所有自由节点,被迫走向一个已经被访问过的节点(某个 \(A_j\) 或其他路径上的点)。在这种情况下,全图所有节点最晚在第 \(n-m\) 步就会被全部访问。因为 \(k > n-m\),所以在第 \(k-1\) 步时,所有节点早已被访问完毕,这与题目条件 \(|V_{k-1}| < n\) 矛盾。
    • 因此,若 \(n-m < k\),则必定无解。

如果上述两个无解条件都不满足,我们就可以用之前提出的策略构造出一组解。

代码实现详解

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const LL N = 2e5 + 5;
LL a[N];
bitset<N> used; // 使用 bitset 标记节点是否为奶龙起始点,空间和时间效率高

int main() {
    LL n, m, k;
    scanf("%lld %lld %lld", &n, &m, &k);

    // 读入 m 个起始点,并用 bitset 标记
    for (int i = 1; i <= m; i++) {
        scanf("%lld", a + i);
        used[a[i]] = 1;
    }

    // 判断无解情况
    if (n > m * (k + 1) || n - m < k) {
        printf("-1\n");
        return 0;
    }

    // ind 是一个指针,用于寻找下一个可用的“自由节点”
    LL ind = 1; 

    // 遍历 m 只奶龙,为它们构建路径
    for (int i = 1; i <= m; i++) {
        // cur 是当前路径的末端节点,len 是当前为这只奶龙构建的链的长度
        LL cur = a[i], len = 0; 
        
        // 尝试为当前奶龙构建一条长度为 k 的新节点链
        // 同时要保证还有可用的自由节点 (ind <= n)
        while (len < k && ind <= n) {
            // 如果 ind 指向的节点不是起始点,就用它来延长路径
            if (!used[ind]) {
                // 构建边:cur -> ind
                printf("%lld %lld\n", cur, ind);
                // 更新路径末端和已构建的长度
                cur = ind;
                len++;
            }
            // 无论 ind 是否被使用,都后移,寻找下一个候选节点
            ind++;
        }
        
        // 当循环结束时,有几种可能:
        // 1. len == k:成功构建了长度为 k 的链。
        // 2. ind > n:所有自由节点都用完了,链的长度可能小于 k。
        // 无论是哪种情况,当前路径的末端 cur 都需要一条出边。
        // 我们将它统一连接到 a[1] (第一个奶龙的起始点)
        printf("%lld %lld\n", cur, a[1]);
    }

    // 此时,所有起始点 a[i] 和所有在链中被用到的自由节点都有了出边。
    // 我们的构造方法保证了 ind 会扫过 1~n,所有 !used[ind] 的节点都会被用上。
    // 这意味着所有 n-m 个自由节点都被编入了某条链中。
    // 因此,所有 n 个节点都有了唯一的出边,构造完成。
    return 0;
}

总结

本题是一道构造型题目,解题的关键在于将复杂的约束转化为一个简单、可控的图结构。通过建立一个“中心辐射”模型(所有路径汇入一个中心点 \(A_1\)),我们得以清晰地控制奶龙的路径和节点的访问情况。

解题步骤为:

  1. 分析题目,理解功能图的结构和“临界访问”的核心要求。
  2. 推导出两个必要的无解条件:\(n > m(k+1)\)(节点总数过多)和 \(n-m < k\)(自由节点不足以构造出长度为 \(k\) 的新路径)。
  3. 如果有解,则采用构造法:以 \(A_1\) 为中心,为每只奶龙 \(A_i\) 贪心地使用未被占用的“自由节点”构建一条路径,并将路径终点统一连接到 \(A_1\)
  4. 代码实现上,使用一个指针 ind 顺序扫描并分配自由节点,逻辑简单且高效。

这种从特殊要求出发,反向设计出一种能满足要求的、简洁的数学模型,是解决构造类问题的常用思想。

posted @ 2025-08-04 22:42  AFewMoon  阅读(57)  评论(0)    收藏  举报