题解:洛谷 P1672 Feed Accounting
【题目来源】
洛谷:P1672 [USACO05FEB] Feed Accounting S - 洛谷
【题目描述】
约翰想知道上一船饲料是什么时候运到的。在饲料运到之前,他的牛正好把仓库里原来的饲料全吃光了。他收到运来的 \(F1(1\le F1\le 10^6)\) 千克饲料。遗憾的是,他已经不记得这是哪一天的事情了。到第 \(D(1\le D\le 2\times 10^3)\) 天为止,仓库里还剩下 \(F2(1\le F2\le F1)\) 千克饲料。
约翰养了 \(C(1\le C\le 100)\) 头牛,每头牛每天都吃掉恰好 \(1\) 千克饲料。由于不同的原因,牛们从某一天开始在仓库吃饲料,又在某一天离开仓库,所以不同的两天可能会有差距很大的饲料消耗量。每头牛在来的那天和离开的那天都在仓库吃饲料。给出今天的日期 \(D\),写一个程序,判断饲料最近一次运到是在什么时候。今天牛们已经吃过饲料了,并且饲料运到的那天牛们还没有吃过饲料。
如果最终的答案有多个可能,请输出最大的(即,最近的)那一个。
【输入】
第 \(1\) 行:四个整数 \(C\),\(F1\),\(F2\),\(D\),用空格隔开。
第 \(2\) 到 \(C+1\) 行:每行是用空格隔开的两个数字,分别表示一头牛来仓库吃饲料的时间和离开的时间。
【输出】
一个正整数,即上一船饲料运到的时间。
【输入样例】
3 14 4 10
1 9
5 8
8 12
【输出样例】
6
【核心思想】
-
问题分析:给定 \(C\) 头牛在仓库的进食时间段、初始饲料量 \(F1\)、第 \(D\) 天剩余量 \(F2\),求饲料最近一次运到的日期。运到当天牛尚未进食,之后每天牛按时间段吃饲料。这是一个差分数组 + 前缀和 + 逆向扫描问题,关键在于用差分统计每天的牛数量,从后往前累加找到恰好消耗 \(F1 - F2\) 饲料的那一天。
-
算法选择:
- 差分数组:将每头牛的进食区间 \([l, r]\) 转化为 \(a[l] \mathrel{+}= 1\)、\(a[r+1] \mathrel{-}= 1\),实现 \(O(1)\) 区间标记
- 前缀和还原:\(b[i] = b[i-1] + a[i]\),得到每天实际在仓库吃饲料的牛数量
- 逆向扫描:从第 \(D\) 天往前累加每天的消耗量 \(b[i]\),找到累计消耗等于 \(F1 - F2\) 的那天即为答案
-
关键步骤:
- 读取输入:\(C, F1, F2, D\) 和 \(C\) 条牛的区间 \([l_i, r_i]\)
- 差分标记:对于每头牛,\(a[l_i] \mathrel{+}= 1\),\(a[r_i + 1] \mathrel{-}= 1\)
- 前缀和还原:\(b[i] = b[i-1] + a[i]\)(\(i\) 从 \(1\) 到 \(D\)),\(b[i]\) 表示第 \(i\) 天吃饲料的牛数
- 逆向扫描(\(i\) 从 \(D\) 递减到 \(1\)):
- \(sum \mathrel{+}= b[i]\),累加从第 \(i\) 天到第 \(D\) 天的总消耗
- 若 \(sum = F1 - F2\):输出 \(i\) 并结束(要求最近日期,逆向首个满足条件的即为最大 \(i\))
-
时间/空间复杂度:
- 时间复杂度:\(O(C + D)\),差分标记 \(O(C)\),前缀和 + 逆向扫描 \(O(D)\)
- 空间复杂度:\(O(D)\),差分数组和前缀和数组
-
差分数组与前缀和的核心思想:
- 区间操作降维:差分数组将 \(C\) 个区间加法转化为 \(2C\) 个单点更新,前缀和一次性还原每日实际值
- 逆向贪心:从后往前扫描保证找到的是"最近"的运到日期(最大的满足条件的 \(i\))
- 总量守恒:\(F1 - F2\) 即为从运到日到第 \(D\) 天的总消耗量,逆向累加找到恰好匹配点
- 适用于"区间覆盖统计 + 逆向查找匹配点"类问题,核心是差分高效处理区间和逆向扫描定位答案
【解题思路】

【算法标签】
普及 #差分
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// 定义常量
const int N = 2005; // 最大天数范围
// 变量定义
int c; // 喂养记录的条数
int f1; // 第一天之前FJ喂养的数量
int f2; // 第d天之后FJ喂养的数量
int d; // 总天数
int a[N]; // 差分数组
int b[N]; // 前缀和数组,表示每天Bessie喂养的次数
int main()
{
// 输入数据
cin >> c >> f1 >> f2 >> d;
// 处理每条喂养记录
for (int i = 1; i <= c; i++)
{
int l, r; // 每条记录的起始天和结束天
cin >> l >> r;
// 使用差分数组
// 在l天开始增加一次喂养
a[l]++;
// 在r+1天减少一次喂养
a[r + 1]--;
}
// 计算前缀和,得到每天Bessie喂养的次数
for (int i = 1; i <= d; i++)
{
b[i] = b[i - 1] + a[i];
}
// 从第d天往前计算
int sum = 0; // 从第i天到第d天的总喂养次数
for (int i = d; i >= 1; i--)
{
sum += b[i]; // 累加第i天的喂养次数
// 检查条件:f1 - sum = f2
// 即:sum = f1 - f2
if (sum == f1 - f2)
{
cout << i << endl; // 输出结果
break;
}
}
return 0;
}
【运行结果】
3 14 4 10
1 9
5 8
8 12
6
浙公网安备 33010602011771号