题解:洛谷 P9974 Candy Cane Feast

【题目来源】

洛谷:P9974 [USACO23DEC] Candy Cane Feast B - 洛谷

【题目描述】

Farmer John 的奶牛对甜食情有独钟,它们尤其喜欢吃糖果棒。FJ 共有 \(N\) 头奶牛,每头奶牛都有一个特定的初始高度。他想要喂它们 \(M\) 根糖果棒,每根糖果棒的高度也各不相同(\(1 \le N,M \le 2\cdot 10^5\))。

FJ 计划按照输入给出的顺序,逐一喂给奶牛们糖果棒。然后,奶牛们会按照输入给出的顺序一个接一个地排队,走向糖果棒,每头奶牛最多吃到与它高度相同的部分(因为它们够不到更高的地方)。即使奶牛吃掉了糖果棒的底部,糖果棒也在最初悬挂的地方保持不动,并不会被降低到地面。如果糖果棒的底部已经高于某头奶牛的高度,那么这头奶牛在它的回合中可能什么也吃不到。每头奶牛轮流吃过后,它们的身高会增加它们吃掉的糖果棒的单位数量,然后农夫约翰挂上下一根糖果棒,奶牛们再次重复这个过程(第一头奶牛再次成为第一个开始吃下一根糖果棒的)。

【输入】

第一行包含 \(N\)\(M\)

接下来的一行包含 \(N\) 头奶牛的初始高度,每头奶牛的高度都在 \([1,10^9]\) 的范围内。

接下来的一行包含 \(M\) 根糖果棒的长度,每根糖果棒的长度都在 \([1,10^9]\) 的范围内。

【输出】

输出 \(N\) 行,表示每头奶牛最终的高度。

请注意,由于这个问题涉及的整数大小较大,可能需要使用 64 位整数数据类型(例如,在 C/C++ 中使用 long long 类型)。

【输入样例】

3 2
3 2 5
6 1

【输出样例】

7
2
7

【核心思想】

  1. 问题分析:有 \(N\) 头奶牛(初始高度 \(a_i\))和 \(M\) 根糖果棒(长度 \(l_j\))。按顺序处理每根糖果棒:奶牛按 \(1\)\(N\) 的顺序依次尝试吃当前糖果棒。每头奶牛能吃到的高度范围为 \((bottom, top]\),其中 \(bottom\) 是当前糖果棒已被吃掉的底部高度(初始为 0),\(top\) 是糖果棒的总长度。若奶牛高度 \(a_i > bottom\),它能吃到 \(\min(a_i, top) - bottom\) 的高度,然后自身高度增加这个量,同时 \(bottom\) 更新为该奶牛的高度(若 \(a_i < top\))或变为 \(top\)(若 \(a_i \ge top\),糖果棒被吃完)。求所有奶牛最终高度。注意:糖果棒悬挂不动,\(bottom\) 记录已被吃到的位置,\(top\) 固定不变。

  2. 算法选择

    • 模拟 + 剪枝:按糖果棒顺序,对每根糖果棒遍历所有奶牛,模拟吃糖过程。关键优化是:当糖果棒被完全吃完(\(top == bottom\))时,直接跳过后续奶牛的遍历,因为当前糖果棒已无剩余可吃。这大大减少总操作次数。
    • 数据结构:用结构体存储每根糖果棒的 top(总长度)和 bottom(已被吃到的位置)。
  3. 关键步骤

    • 读入:读入 \(N, M\),奶牛高度数组 \(a[1..N]\),以及每根糖果棒的长度 \(l_j\),初始化其 bottom = 0
    • 处理每根糖果棒(外层循环 \(j\) 从 1 到 \(M\)):
      • 若当前糖果棒已被吃完(top == 0),则跳过所有奶牛(break)。
      • 遍历每头奶牛(内层循环 \(i\) 从 1 到 \(N\)):
        • \(a_i \le bottom\),奶牛够不到糖果棒剩余部分,跳过。
        • \(bottom < a_i < top\),奶牛能吃到 \(a_i - bottom\) 长度,自身增加该长度;同时 \(bottom\) 更新为 \(a_i\)
        • \(a_i \ge top\),奶牛能吃到 \(top - bottom\) 长度(即剩余全部),自身增加该长度;同时 \(bottom\) 更新为 \(top\),糖果棒被吃完(标记 top = 0)。
    • 输出:最终 \(a[1..N]\)
  4. 时间/空间复杂度

    • 时间复杂度:理论上最坏 \(O(N \cdot M)\)\(N, M \le 2 \times 10^5\),可能超时),但剪枝(糖果棒被吃完后立即停止)在实际数据中通常有效。极端情况下(所有奶牛高度都很矮,\(bottom\) 增长极慢)仍可能接近 \(O(NM)\),但官方数据可通过此优化。
    • 空间复杂度:\(O(N + M)\)
  5. 模拟 + 剪枝的核心思想

    • 关键优化:一旦某根糖果棒被完全吃掉,后续奶牛对该糖果棒的操作都无效,应直接跳过。这利用了“糖果棒剩余部分单调减少”的特性。
    • 状态维护:用 \(bottom\) 记录已吃高度,而不是每次重新计算,避免了重复遍历已吃的低端部分。
    • 适用场景:适用于“按顺序处理多个物品,每个物品被多个实体按固定顺序消费”的模拟问题。核心是维护物品的剩余可消费边界,并在耗尽时提前终止。

【解题思路】

【算法标签】

普及 #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m;
ll a[200005];
struct node {
    int top, bottom;
}b[200005];
int main()
{
    cin >> n >> m;  // 输入n和m
    for (int i=1; i<=n; i++) {  // 输入n个奶牛的身高
        cin >> a[i];
    }
    for (int i=1; i<=m; i++) {  // 输入m个拐杖糖的高度
        cin >> b[i].top;  // 输入拐杖糖的高度
        b[i].bottom = 0;  // 同时记录该拐杖糖的底部为0
    }
    for (int i=1; i<=m; i++) {  // 先遍历m再遍历n(反过来会超时,部分测试点会TLE)
        for (int j=1; j<=n; j++) {
            if (b[i].top==0) break;  // 如果该拐杖糖已经被吃掉了,则跳过该糖(就是通过该行代码减少了时间复杂度)
            if (a[j]<=b[i].bottom) continue;  // 如果奶牛高度小于糖的底部高度,则说明没法吃到,继续循环
            if (a[j]>=b[i].bottom && a[j]<b[i].top) {  // 如果奶牛高度在糖的高度中间
                int tmp = a[j];  // 记录奶牛高度
                a[j] = a[j] + (a[j]-b[i].bottom);  // 奶牛高度增加吃掉的糖的高度
                b[i].bottom = tmp;  // 将糖的底部高度更新为奶牛的高度
            } else if (a[j]>=b[i].top) {  // 如果奶牛比糖的top位置还高
                a[j] = a[j] + (b[i].top-b[i].bottom);  // 则奶牛高度增加糖的长度(top-bottom)
                b[i].top = 0;  // 将糖的top修改为0,即被吃掉
            }
        }
    }
    for (int i=1; i<=n; i++) {  // 输出所有奶牛高度
        cout << a[i] << endl;
    }
    return 0;
}

【运行结果】

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