题解:洛谷 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
【核心思想】
-
问题分析:有 \(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\) 固定不变。
-
算法选择:
- 模拟 + 剪枝:按糖果棒顺序,对每根糖果棒遍历所有奶牛,模拟吃糖过程。关键优化是:当糖果棒被完全吃完(\(top == bottom\))时,直接跳过后续奶牛的遍历,因为当前糖果棒已无剩余可吃。这大大减少总操作次数。
- 数据结构:用结构体存储每根糖果棒的
top(总长度)和bottom(已被吃到的位置)。
-
关键步骤:
- 读入:读入 \(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]\)。
- 读入:读入 \(N, M\),奶牛高度数组 \(a[1..N]\),以及每根糖果棒的长度 \(l_j\),初始化其
-
时间/空间复杂度:
- 时间复杂度:理论上最坏 \(O(N \cdot M)\)(\(N, M \le 2 \times 10^5\),可能超时),但剪枝(糖果棒被吃完后立即停止)在实际数据中通常有效。极端情况下(所有奶牛高度都很矮,\(bottom\) 增长极慢)仍可能接近 \(O(NM)\),但官方数据可通过此优化。
- 空间复杂度:\(O(N + M)\)。
-
模拟 + 剪枝的核心思想:
- 关键优化:一旦某根糖果棒被完全吃掉,后续奶牛对该糖果棒的操作都无效,应直接跳过。这利用了“糖果棒剩余部分单调减少”的特性。
- 状态维护:用 \(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
浙公网安备 33010602011771号