题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(N\) 次送干草事件,第 \(d_i\) 天早晨送来 \(b_i\) 捆,Bessie 每天晚上(若谷仓有干草)吃掉 1 捆。求前 \(T\) 天(\(T \le 10^{14}\))内总共吃掉多少捆。这是一个带补给的时间轴贪心模拟问题,关键在于不逐天模拟,而是按送草事件的间隔批量计算消耗量。

  2. 算法选择

    • 贪心模拟 + 批量处理:按时间顺序处理每次送草事件,维护当前谷仓中剩余的干草捆数 \(rest\)。在两个送草日之间的区间内,Bessie 每天最多吃掉 1 捆,因此可消耗的干草上限为区间天数。用 \(O(N)\) 的时间跳过 \(T\) 天内的所有日子。
    • 64位整数:所有变量使用 long long,因为 \(T\)\(b_i\) 均可达到 \(10^{14}\) 级别。
  3. 关键步骤(基于学而思课堂笔记的简洁实现):

    • 预处理:读入 \(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),其中 \(N \le 10^5\)
    • 空间复杂度:\(O(N)\)\(O(1)\)(可流式处理,但示例代码存储了数组)。若流式读取则空间 \(O(1)\)
  5. 贪心批量模拟的核心思想

    • 跳过空闲日:不逐天模拟 \(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
posted @ 2026-08-29 09:41  团爸讲算法  阅读(4)  评论(0)    收藏  举报