题解:洛谷 P1204 挤牛奶
【题目来源】
洛谷:P1204 [USACO1.2] 挤牛奶Milking Cows - 洛谷
【题目描述】
三个农民每天清晨 5 点起床,然后去牛棚给三头牛挤奶。
第一个农民在 300 秒(从 5 点开始计时)给他的牛挤奶,一直到 1000 秒。第二个农民在 700 秒开始,在 1200 秒结束。第三个农民在 1500 秒开始,2100 秒结束。
期间最长的至少有一个农民在挤奶的连续时间为 900 秒(从 300 秒到 1200 秒),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为 300 秒(从 1200 秒到 1500 秒)。
你的任务是编一个程序,读入一个有 n 个农民挤 n 头牛的工作时间列表,计算以下两点(均以秒为单位):
最长至少有一人在挤奶的时间段。
最长的无人挤奶的时间段。(从有人挤奶开始算起)
【输入】
第一行一个正整数 n
接下来 n 行,每行两个非负整数 l,r,表示一个农民的开始时刻与结束时刻。
【输出】
一行,两个整数,即题目所要求的两个答案。
【输入样例】
3
300 1000
700 1200
1500 2100
【输出样例】
900 300
【核心思想】
-
问题分析:给定 \(n\) 个农民挤奶的时间段 \([l_i, r_i]\),要求找出两个值:一是至少有一人在挤奶的最长连续时间段,二是无人挤奶的最长连续时间段(在整体挤奶时间范围内)。这是一个经典的区间合并问题,关键在于将重叠或相邻的区间合并后统计连续段和间隔段。
-
算法选择:
- 按起点排序:将所有区间按开始时间从小到大排序,保证区间按时间顺序处理
- 贪心合并:维护当前合并区间 \([start, last]\),若下一个区间与当前区间重叠或相邻,则扩展当前区间
- 统计间隔:若下一个区间与当前区间不重叠,则两者之间的空隙就是一个无人挤奶时间段
- 边合并边更新:在遍历过程中同步更新最长有人挤奶段 \(t1\) 和最长无人挤奶段 \(t2\)
-
关键步骤:
- 读取输入:\(n\) 个区间 \([l_i, r_i]\)
- 按开始时间排序:使用自定义比较函数按 \(a\) 升序排列
- 初始化当前区间:\(start = f[1].a\),\(last = f[1].b\),\(t1 = last - start\)
- 遍历合并(\(i\) 从 \(2\) 到 \(n\)):
- 重叠/相邻:若 \(f[i].a \le last\),则 \(last = \max(last, f[i].b)\),并更新 \(t1 = \max(t1, last - start)\)
- 不重叠:若 \(f[i].a > last\),则 \(t2 = \max(t2, f[i].a - last)\),然后开启新区间 \(start = f[i].a\),\(last = f[i].b\)
- 输出结果:\(t1\) 和 \(t2\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \log n)\),主要由排序决定,合并过程为 \(O(n)\)
- 空间复杂度:\(O(n)\),存储 \(n\) 个区间
-
区间合并的核心思想:
- 排序保证顺序性:按开始时间排序后,所有区间按时间先后处理,避免遗漏或重复
- 贪心维护当前段:只关心当前连续有人挤奶的区间 \([start, last]\),遇到重叠就扩展,不重叠就结算
- 连续与间隔的对偶性:合并后的连续段对应 \(t1\),段与段之间的空隙对应 \(t2\)
- 相邻即连续:当 \(f[i].a = last\) 时认为仍连续,符合"至少有一人在挤奶"的定义
- 适用于区间覆盖、区间重叠、时间线段合并、日程安排等问题
【解题思路】

【算法标签】
普及- #其他排序
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, start, last, tmp;
int t1=0, t2=0; // 设置t1为至少一人挤奶的时间段,t2位无人挤奶的时间段
struct farmer {
int a, b;
}f[5005];
bool cmp (farmer x, farmer y) // 定义排序方式
{
return x.a < y.a;
}
int main()
{
cin >> n; // 输入n
for (int i=1; i<=n; i++) { // 输入n个农民的开始和结束时间
cin >> f[i].a >> f[i].b;
}
sort(f+1, f+n+1, cmp); // 按照开始时间从小到大方式排序
start = f[1].a; // 定义起始和结束时间
last = f[1].b;
t1 = last - start; // 并计算t1
for (int i=2; i<=n; i++) { // 从第2人开始遍历
if (f[i].a<=last) { // 如果这人的开始时间小于等于结束时间
last = max(f[i].b, last); // 则更新结束时间,取最大值
t1 = max(t1, last - start); // 更新t1时间
} else if (f[i].a>last) { // 否则,说明开始时间大于结束时间
t2 = max(t2, f[i].a - last); // 更新t2
start = f[i].a; // 并更新开始和结束时间,后面要重新计算
last = f[i].b;
}
}
cout << t1 << " " << t2; // 输出t1和t2
return 0;
}
【运行结果】
3
300 1000
700 1200
1500 2100
900 300
浙公网安备 33010602011771号