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

【核心思想】

  1. 问题分析:给定 \(n\) 个农民挤奶的时间段 \([l_i, r_i]\),要求找出两个值:一是至少有一人在挤奶的最长连续时间段,二是无人挤奶的最长连续时间段(在整体挤奶时间范围内)。这是一个经典的区间合并问题,关键在于将重叠或相邻的区间合并后统计连续段和间隔段。

  2. 算法选择

    • 按起点排序:将所有区间按开始时间从小到大排序,保证区间按时间顺序处理
    • 贪心合并:维护当前合并区间 \([start, last]\),若下一个区间与当前区间重叠或相邻,则扩展当前区间
    • 统计间隔:若下一个区间与当前区间不重叠,则两者之间的空隙就是一个无人挤奶时间段
    • 边合并边更新:在遍历过程中同步更新最长有人挤奶段 \(t1\) 和最长无人挤奶段 \(t2\)
  3. 关键步骤

    • 读取输入\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),主要由排序决定,合并过程为 \(O(n)\)
    • 空间复杂度:\(O(n)\),存储 \(n\) 个区间
  5. 区间合并的核心思想

    • 排序保证顺序性:按开始时间排序后,所有区间按时间先后处理,避免遗漏或重复
    • 贪心维护当前段:只关心当前连续有人挤奶的区间 \([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
posted @ 2026-07-30 09:36  团爸讲算法  阅读(3)  评论(0)    收藏  举报