题解:洛谷 P2879 Tallest Cow

【题目来源】

洛谷:P2879 [USACO07JAN] Tallest Cow S - 洛谷

【题目描述】

FJ 的 \(N\) 头奶牛(\(1 \le N \le 10,000\))按编号 \(1\)\(N\) 排成一行。每头奶牛有一个正整数表示的身高(这是个秘密)。现在你只知道最高奶牛的身高 \(H\)\(1 \le H \le 1,000,000\)),以及它的编号 \(I\)

FJ 提供了 \(R\) 条信息(\(0 \le R \le 10,000\)),每条信息形如“奶牛 17 能看到奶牛 34”。这意味着奶牛 34 的身高不小于奶牛 17 的身高,并且编号在 17 和 34 之间的所有奶牛,其身高都严格小于奶牛 17 的身高。

现在请你计算出对于每一头奶牛(编号从 \(1\)\(N\)),在所有给定信息都成立的前提下,它可能具有的最大身高。

题目保证一定存在满足条件的解。

【输入】

\(1\) 行:四个用空格分隔的整数:\(N\)\(I\)\(H\)\(R\)

\(2\) 到第 \(R+1\) 行:每行两个不同的整数 \(A\)\(B\)\(1 \le A\), \(B \le N\)),表示奶牛 \(A\) 能看到奶牛 \(B\)

【输出】

\(N\) 行,第 \(i\) 行输出奶牛 \(i\) 的最大可能身高。

【输入样例】

9 3 5 5
1 3
5 3
4 3
3 7
9 8

【输出样例】

5
4
5
3
4
4
5
5
5

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛排成一行,已知最高奶牛编号 \(I\) 身高 \(H\),以及 \(R\) 条"能看到"信息。若 \(A\) 能看到 \(B\),则 \(height[B] \ge height[A]\),且 \(A\)\(B\) 之间的所有奶牛身高严格小于 \(height[A]\)。求每头奶牛的最大可能身高。这是一个差分数组 + 区间约束问题,关键在于将"能看到"关系转化为区间高度限制,用差分数组高效处理。

  2. 算法选择

    • 差分数组:每条"能看到"信息意味着 \((A, B)\) 开区间内的所有奶牛身高至少比两端低 \(1\),用差分标记区间减 \(1\)
    • 去重处理:用 set 存储已处理的区间,避免重复信息导致过度递减
    • 前缀和还原:差分数组前缀和得到每头奶牛相对于基准的累计高度调整
  3. 关键步骤

    • 读取输入\(N, I, H, R\)\(R\) 条信息 \((A, B)\)
    • 处理每条信息
      • 确保 \(A < B\)(排序)
      • \((A, B)\) 已在 set 中:跳过(去重)
      • 否则:\(dif[A+1] \mathrel{-}= 1\)\(dif[B] \mathrel{+}= 1\)(区间 \((A, B)\) 内所有奶牛高度减 \(1\)
    • 前缀和还原\(i\)\(2\)\(N\)):\(dif[i] \mathrel{+}= dif[i-1]\)
    • 输出:每头奶牛身高 = \(H + dif[i]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(R \log R + N)\)set 去重 \(O(R \log R)\),前缀和与输出 \(O(N)\)
    • 空间复杂度:\(O(N + R)\),差分数组和去重集合
  5. 差分区间约束的核心思想

    • "能看到"的约束转化\(A\) 能看到 \(B\) 意味着 \((A, B)\) 内的奶牛是"视野遮挡物",它们必须比 \(A\) 矮,即这些奶牛的身高上限被降低
    • 最大化身高策略:在不影响约束的前提下,让每头奶牛尽可能高。由于 \(I\) 是最高的,其他奶牛的身高由约束决定其最大可能值
    • 差分高效区间操作:将 \(R\) 个区间减 \(1\) 操作转化为 \(2R\) 个单点更新,前缀和一次性还原
    • 去重必要性:同一区间被多次提及不应重复约束,否则会导致高度过度降低
    • 适用于"区间约束 + 最大化目标值"类问题,核心是将约束转化为差分标记并去重

【算法标签】

普及 #差分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

int N;      // 牛的数量
int I;      // 初始高度
int H;      // 基准高度
int R;      // 观察次数
int dif[10005];  // 差分数组,记录高度变化
set<pair<int, int> > s;  // 用于去重的集合,存储已处理的区间

int main()
{
    // 输入牛的数量、初始高度、基准高度和观察次数
    cin >> N >> I >> H >> R;

    // 处理每个观察记录
    for (int i = 1; i <= R; i++)
    {
        int A, B;  // 观察到的两头牛的编号
        cin >> A >> B;

        // 确保A是较小的编号,B是较大的编号
        if (A > B)
        {
            swap(A, B);
        }

        // 检查是否已处理过相同的区间(去重)
        if (s.count({A, B}) != 0)
        {
            continue;  // 跳过重复的观察记录
        }

        // 将区间加入集合,标记为已处理
        s.insert({A, B});

        // 差分数组操作:A+1到B-1区间的牛高度减1
        dif[A + 1]--;  // 从A+1位置开始高度减少
        dif[B]++;      // 到B位置结束高度减少
    }

    // 计算前缀和,将差分数组转换为实际高度变化
    for (int i = 2; i <= N; i++)
    {
        dif[i] += dif[i - 1];
    }

    // 输出每头牛的最终高度
    for (int i = 1; i <= N; i++)
    {
        cout << H + dif[i] << endl;
    }

    return 0;
}

【运行结果】

9 3 5 5
1 3
5 3
4 3
3 7
9 8
5
4
5
3
4
4
5
5
5
posted @ 2026-08-28 15:55  团爸讲算法  阅读(3)  评论(0)    收藏  举报