题解:洛谷 P9121 Hungry Cow
【题目来源】
洛谷:P9121 [USACO23FEB] Hungry Cow B - 洛谷
【题目描述】
Bessie 是一头饥饿的奶牛。每天晚餐时,如果谷仓中有干草,她会吃掉一捆干草。为了防止 Bessie 挨饿,有些天 Farmer John 会在早晨(晚餐之前)送来一些干草。具体地说,在第 \(d_i\) 天,Farmer John 送来了 \(b_i\) 捆干草(\(1 \leq d_i \leq 10^{14}, 1 \leq b_i \leq 10^9\))。
请计算 Bessie 在前 \(T\) 天内共吃掉了多少捆干草。
【输入】
第一行包含两个整数 \(N\) 和 \(T\)(\(1 \leq N \leq 10^5, 1 \leq T \leq 10^{14}\))。
接下来的 \(N\) 行每行包含两个整数 \(d_i\) 和 \(b_i\)。保证 \(1 \leq d_1 < d_2 < \cdots < d_N \leq T\)。
【输出】
输出 Bessie 在前 \(T\) 天内吃掉的干草总数。
注意,本题中涉及的整数可能非常大,可能需要使用 64 位整数类型(例如 C/C++ 中的 "long long")。
【输入样例】
1 5
1 2
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 次送干草事件,第 \(d_i\) 天早晨送来 \(b_i\) 捆,Bessie 每天晚上(若谷仓有干草)吃掉 1 捆。求前 \(T\) 天(\(T \le 10^{14}\))内总共吃掉多少捆。这是一个带补给的时间轴贪心模拟问题,关键在于不逐天模拟,而是按送草事件的间隔批量计算消耗量。
-
算法选择:
- 贪心模拟 + 批量处理:按时间顺序处理每次送草事件,维护当前谷仓中剩余的干草捆数 \(rest\)。在两个送草日之间的区间内,Bessie 每天最多吃掉 1 捆,因此可消耗的干草上限为区间天数。用 \(O(N)\) 的时间跳过 \(T\) 天内的所有日子。
- 64位整数:所有变量使用
long long,因为 \(T\) 和 \(b_i\) 均可达到 \(10^{14}\) 级别。
-
关键步骤(基于学而思课堂笔记的简洁实现):
- 预处理:读入 \(N, T\),并设置哨兵 \(d_0 = 0\)(表示第 0 天,无干草)。
- 初始化:\(rest = 0\)(当前谷仓干草数),\(ans = 0\)(累计吃掉量)。
- 循环处理每次送草(\(i = 1 \dots N\)):
- 计算当前与前一次送草之间的可吃草天数:\(days = d_i - d_{i-1}\)。注意,从 \(d_{i-1}\) 的晚餐后到 \(d_i\) 的早餐前,有 \(d_i - d_{i-1}\) 个晚餐时刻(若 \(d_{i-1}=0\),表示第 1 天早餐前无干草,但第 1 天早餐时收到干草,所以从第 1 天晚餐开始吃,天数差为 1,符合 \(d_1 - 0\))。
- 计算这 \(days\) 天内实际吃掉的干草数:\(eat = \min(rest, days)\)。因为每天最多吃 1 捆。
- \(rest -= eat\),\(ans += eat\)。
- 在第 \(d_i\) 天早餐时收到 \(b_i\) 捆:\(rest += b_i\)。
- 处理最后一次送草后到第 \(T\) 天:\(days = T - d_N + 1\)(包括第 \(d_N\) 天晚餐到第 \(T\) 天晚餐的所有天数,因为第 \(d_N\) 天早餐已收到干草,当天晚餐即可开始吃)。\(ans += \min(rest, days)\)。
- 输出 \(ans\)。
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),其中 \(N \le 10^5\)。
- 空间复杂度:\(O(N)\) 或 \(O(1)\)(可流式处理,但示例代码存储了数组)。若流式读取则空间 \(O(1)\)。
-
贪心批量模拟的核心思想:
- 跳过空闲日:不逐天模拟 \(T\) 天(\(T\) 可达 \(10^{14}\)),而是仅在送草日之间进行区间批量计算。
- 干草存量驱动:每天吃干草的数量只取决于当前谷仓中是否有干草(有则吃 1 捆),这与时间轴上的具体日期无关,只与送草事件的间隔有关。
- 正确性保证:由于 Bessie 每天最多消耗 1 捆,且送草只在早晨发生,因此在两个送草日之间,她能吃掉的干草数等于 \(\min(rest, 间隔天数)\),这是贪心策略的最优体现——不浪费任何可吃的干草。
- 边界处理:特别注意左闭右开区间转换为天数的 +1/-1 细节。课堂笔记代码中 \(d_i - d_{i-1}\) 和 \(T - d_N + 1\) 的处理精确包含了第 \(d_i\) 天晚餐的消耗。
- 适用于时间跨度极大、事件稀疏的连续消耗问题,核心是通过事件间隔批量计算而不是逐单位时间模拟。
【解题思路】

【算法标签】
普及- #贪心
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n;
long long t, nowd, nowb, lastd, lastb, ans=0;
int main()
{
cin >> n >> t; // 输入n和t
cin >> lastd >> lastb; // 输入第i天,以及干草堆数,将其标记为last,后面循环每次都需要和前一天进行比较
if (n==1) { // 特判n为1,直接输出干草堆数
cout << lastb << endl;
return 0;
}
for (int i=2; i<=n; i++) { // for循环遍历之后n-1次
cin >> nowd >> nowb; // 输入第i天,以及这天的干草堆数
if (nowd<t) { // 如果这一天比询问的t天小
if ((nowd-lastd)>lastb) { // 则判断与上次的天数之差大于干草堆数
ans += lastb; // 说明干草不够相差的这么多天每天吃,只能吃之前的干草堆数
lastb = nowb; // 更新lastb和lastd
lastd = nowd;
} else { // 如果与上次的天数之差小于等于干草堆数
ans += (nowd-lastd); // 则吃了这么多天的干草
lastb = lastb - (nowd-lastd) + nowb; // 剩余的干草要添加到lastb中
lastd = nowd; // 更新lastd
}
}
else if (nowd==t) { // 如果输入的天数为t天
if ((nowd-lastd)>lastb) { // 同样判断与上次的天数之差大于干草堆数
ans += lastb; // 说明干草不够相差的这么多天每天吃,只能吃之前的干草堆数
ans += 1; // 第t天还需要再吃一堆
cout << ans << endl; // 输出结果
return 0; // 并退出程序
} else { // 如果与上次的天数之差小于等于干草堆数
ans += (nowd-lastd); // 则吃了这么多天的干草
ans += 1; // 同样第t天需要再吃一堆
cout << ans << endl; // 输出结果
return 0; // 并退出程序
}
}
}
if (t>lastd) { // 如果t大于n次数的最后一天,则一样要做判断
if ((t-lastd)>lastb) { // 如果与上次的天数之差大于干草堆数
ans += lastb; // 说明只能吃之前的干草堆数
} else { // 如果小于等于干草堆数
ans += (t-lastd)+1; // 则吃了相差天数+1的干草
}
}
cout << ans << endl; // 输出结果
return 0;
}
// 学而思课堂笔记
#include <bits/stdc++.h>
using namespace std;
int n;
long long t, rest, cnt, ans, d[100005], b[100005];
int main()
{
cin >> n >> t;
for (int i=1; i<=n; i++) { // 循环枚举每次送干草信息
cin >> d[i] >> b[i]; // 输入本次送干草时间d[i]和数量b[i]
cnt = min(rest, d[i]-d[i-1]); //计算上一次送甘草和这次送甘草之间奶牛吃掉的干草数量cnt
rest -= cnt; // 吃掉这部分干草
ans += cnt;
rest += b[i]; // 加上这次新送来的干草
}
cout << ans + min(rest, t-d[n]+1) << endl; // 加上最后一次送干草之后吃掉的干草数量并输出
return 0;
}
【运行结果】
1 5
1 2
2
浙公网安备 33010602011771号