题解:洛谷 P10134 Cowmpetency

【题目来源】

洛谷:P10134 [USACO24JAN] Cowmpetency S - 洛谷

【题目描述】

Farmer John 正在为他的奶牛们雇用一位新的牛群领队。为此,他面试了 \(N\)\(2\le N\le 10^5\))头奶牛来担任该职位。在面试第 \(i\) 个候选牛后,他会为候选牛分配一个 \(1\)\(C\)\(1\le C\le 10^9\))范围内的整数「牲任力」分数 \(c_i\),与她们的领导能力相关。

由于 Farmer John 面试了如此多的奶牛,他没能记得所有奶牛的牲任力分数。然而,他确实记得 \(Q\)\(1\le Q<N\))对数字 \((a_j,h_j)\),其中奶牛 \(h_j\) 是第一头比奶牛 \(1\)\(a_j\) 拥有严格更高牲任力分数的奶牛(所以 \(1\le a_j<h_j\le N\))。

Farmer John 现在告诉你序列 \(c_1,\ldots,c_N\)
(其中 \(c_i=0\) 表示他忘记了奶牛 \(i\) 的牲任力分数)和 \(Q\)\((a_j,h_j)\)。帮助他求出与此信息一致的最小字典序的牲任力分数序列,或者判断不存在这样的序列!如果一个分数序列比另一个分数序列于这两个序列不同的第一个位置上的奶牛分配了更小的分数,则这个分数序列的字典序更小。

每个测试点包含 \(T\)\(1\le T\le 20\))个独立的测试用例。输入保证所有测试用例的 \(N\) 之和不超过 \(3\cdot 10^5\)

【输入】

输入的第一行包含 \(T\),独立的测试用例的数量。每个测试用例描述如下:

  1. 第一行包含 \(N\)\(Q\)\(C\)
  2. 第二行包含序列 \(c_1,\ldots,c_N\)\(0\le c_i\le C\))。
  3. 最后 \(Q\) 行,每行包含一个数对 \((a_j,h_j)\)。输入保证同一测试用例内的所有 \(a_j\) 各不相同。

【输出】

对于每个测试用例,输出一行,包含与 Farmer John 记忆一致的最小字典序牲任力分数序列,或者如果这样的序列不存在时输出 \(−1\)

【输入样例】

1
7 3 5
1 0 2 3 0 4 0
1 2
3 4
4 5

【输出样例】

1 2 2 3 4 4 1

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的序列 \(c_1, \dots, c_N\)\(c_i = 0\) 表示遗忘),以及 \(Q\) 个约束 \((a_j, h_j)\)\(h_j\) 是第 \(1\)\(a_j\) 中第一个比它们都严格大的位置。求最小字典序的合法序列,或判断无解。本质上是贪心构造 + 约束验证问题:按字典序最小原则逐步填充 \(0\),同时满足所有"严格更大"的约束条件。

  2. 算法选择

    • 约束预处理\(b[i]\) 记录覆盖位置 \(i\) 的最早操作起始点 \(x+1\)(即约束区间 \([x+1, h_j]\)\(h_j\) 处为峰值)
    • 两遍扫描:第一遍贪心填充 \(0\) 并维护区间最大值,第二遍验证所有约束是否满足
  3. 关键步骤

    • 读取数据:读入 \(T\) 组数据,每组读入 \(N, Q, C\) 和序列 \(c[1..N]\)
    • 预处理约束:对每个 \((x, y)\)\(b[y] = \min(b[y], x+1)\),记录覆盖 \(y\) 的最早起始点
    • 第一遍填充\(i = 1\)\(N\)):
      • \(b[i]\) 被覆盖且 \(b[i] \leq last\),则约束重叠,无解
      • 处理 \([last, b[i]-1]\) 区间,记录最大值 \(mx\) 和最后一个 \(0\) 的位置 \(zero\)
      • 处理 \([b[i], i-1]\) 区间,记录最大值 \(tm\)
      • \(tm > mx\),将 \(a[zero]\) 设为 \(tm\)(保证峰值严格大于前缀)
      • \(a[i] = 0\),设 \(a[i] = mx + 1\)(峰值至少比前缀大 \(1\)
      • 更新 \(last = i\)
    • 填充剩余 \(0\)\(1\)
    • 第二遍验证
      • 重新遍历,检查每个约束区间的最大值是否等于预期峰值,且峰值严格大于前缀
      • 检查全局最大值是否 \(\leq C\)
    • 输出合法序列或 \(-1\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(T \cdot N^2)\),最坏情况每遍扫描 \(O(N^2)\)
    • 空间复杂度:\(O(N)\),数组存储
  5. 贪心构造 + 约束验证的核心思想

    • 字典序最小策略:未覆盖的 \(0\) 尽量填 \(1\),被约束覆盖的位置在满足条件前提下也尽量小
    • 峰值维护:约束要求 \(h_j\) 处为区间 \([1, a_j]\) 的严格最大值,通过维护前缀最大值 \(mx\) 和区间最大值 \(tm\) 确保构造合法
    • 两遍扫描的必要性:第一遍贪心填充可能因后续约束而局部最优非全局最优,第二遍验证可检测矛盾
    • 约束重叠检测:若两个约束的覆盖区间重叠,则无法同时满足"各自为前缀严格最大",直接判定无解
    • 适用于序列构造、约束满足、字典序优化类问题

【解题思路】

【算法标签】

普及+ #贪心

【代码详解】

// 学而思课堂笔记,但是只能过2个测试点
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;

int n, q, c, a[N], b[N];
// a[i] -- 第 i 个位置的值
// b[i] -- 记录覆盖位置 i 的最早操作起始点

int main()
{
    int T;
    scanf("%d", &T); // 输入测试用例数量
    while (T--)
    {
        scanf("%d%d%d", &n, &q, &c); // 输入 n, q, c
        memset(b, 0x3f, sizeof(b)); // 初始化 b 数组为极大值

        // 输入初始数组 a
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
        }

        // 处理 q 次操作,记录覆盖位置 y 的最早起始点 x+1
        for (int i = 1; i <= q; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            b[y] = min(b[y], x + 1);
        }

        int last = 1, mx = 1, flag = true;
        // last -- 上一个处理的位置
        // mx -- 当前最大值
        // flag -- 标记是否存在解

        // 第一遍处理:填充未覆盖区间并检查合法性
        for (int i = 1; i <= n; i++)
        {
            if (b[i] != 0x3f3f3f3f) // 如果位置 i 被操作覆盖
            {
                int zero = 0;
                if (b[i] <= last) // 检查操作是否重叠
                {
                    flag = false;
                    break;
                }

                // 处理 [last, b[i]-1] 区间,记录最大值和最后一个 0 的位置
                for (int j = last; j < b[i]; j++)
                {
                    if (!a[j]) zero = j;
                    mx = max(mx, a[j]);
                }

                // 处理 [b[i], i-1] 区间,记录最大值
                int tm = 0;
                for (int j = b[i]; j < i; j++)
                {
                    tm = max(tm, a[j]);
                }

                // 如果 [b[i], i-1] 的最大值大于 [last, b[i]-1] 的最大值,
                // 则将最后一个 0 设置为 tm
                if (tm > mx)
                {
                    mx = a[zero] = tm;
                }

                // 如果 a[i] 为 0,则设置为 mx + 1
                if (!a[i])
                {
                    a[i] = mx + 1;
                    zero = i;
                }

                last = i; // 更新 last
            }
        }

        // 填充剩余未处理的 0 为 1
        for (int i = 1; i <= n; i++)
        {
            if (!a[i]) a[i] = 1;
        }

        // 如果之前发现非法情况,输出 -1
        if (!flag)
        {
            puts("-1");
            continue;
        }

        // 第二遍处理:验证解的合法性
        last = 1, mx = 1;
        for (int i = 1, lm = 1; i <= n; i++)
        {
            if (b[i] != 0x3f3f3f3f)
            {
                // 计算 [last, b[i]-1] 区间的最大值
                while (last < b[i])
                {
                    lm = max(lm, a[last++]);
                }
                // 检查是否满足条件
                if (mx != lm || a[i] <= mx)
                {
                    flag = false;
                }
            }
            mx = max(mx, a[i]); // 更新全局最大值
        }

        // 输出结果
        if (!flag || mx > c)
        {
            puts("-1");
        }
        else
        {
            for (int i = 1; i < n; i++)
            {
                printf("%d ", a[i]);
            }
            printf("%d\n", a[n]);
        }
    }
    return 0;
}

【运行结果】

1
7 3 5
1 0 2 3 0 4 0
1 2
3 4
4 5
1 2 2 3 4 4 1
posted @ 2026-08-17 09:44  团爸讲算法  阅读(9)  评论(0)    收藏  举报