USACO
时间限制: 1 s
空间限制: 128000 KB
题目等级 : 青铜 Bronze
题目链接
题目描述 Description
三个农民每天清晨5点起床,然后去牛棚给3头牛挤奶。第一个农民在300时刻(从5点开始计时,秒为单位)给他的牛挤奶,一直到1000时刻。第二个农民在700时刻开始,在 1200时刻结束。第三个农民在1500时刻开始2100时刻结束。期间最长的至少有一个农民在挤奶的连续时间为900秒(从300时刻到1200时刻),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为300秒(从1200时刻到1500时刻)。
你的任务是编一个程序,读入一个有N个农民(1 <= N <= 5000)挤N头牛的工作时间列表,计算以下两点(均以秒为单位):
- 最长至少有一人在挤奶的时间段。
- 最长的无人挤奶的时间段。
输入描述 Input Description
Line 1: 一个整数N。 Lines 2…N+1: 每行两个小于1000000的非负整数,表示一个农民的开始时刻与结束时刻。
输出描述 Output Description
一行,两个整数,即题目所要求的两个答案。
样例输入 Sample Input
3
300 1000
700 1200
1500 2100
样例输出 Sample Output
900 300
数据范围及提示 Data Size & Hint
解题思路
两种:
-
由于题目规定时间一共只有1000000时刻,即其实可以申请一百万个
bool
变量描述每一个时刻,读入一个时段时,将整个时段的bool
置为true
,最后只要简单的扫描最长的true
及false
即可得出答案,编写比较简单,也很容易懂,但需要较多空间。 -
将每个时段都当做一段横线,即求最长的重叠线段,那么可以只记录时段的起始值,通过扫描将所有重叠的时段合并即可。最后排序一下,然后只要遍历一次就可以获得结果。
无法合并的时段之间一定存在间隔,那么只要求间隔的最大值和剩余时段中的最大值即可得到答案,比第一种方法更省空间,在这种问题规模上效率不好说哪种方法更好,这一种方法时间复杂度不好求,因为合并本身可能产生新的可合并的时段。
由于个人有点空间紧张癖(滑稽),因此使用第二种方法。
AC代码
/*
作者:GarfieldGCat
题目:p1385 挤牛奶
*/
#include <stdio.h>
#include <vector>
#include <algorithm>
using namespace std;
struct timeFrame {
int begin;
int end;
};
//注意,比较对象在codevs系统中不可用,只能用比较函数。
bool comparer(timeFrame a, timeFrame b){ return a.begin < b.begin; }
int main(void)
{
vector<timeFrame> f;
int n;
scanf_s("%d", &n);
timeFrame tmp;
while (n--)
{
scanf_s("%d %d", &tmp.begin, &tmp.end);
bool isCombined = true;
while (isCombined)
{
isCombined = false;
//当一个新的时段进入时,查找已有列表中是否有能与之合并的时段,有则合并,直至找不到可合并的时段才插入,即使有重叠的时段通通合并为一个再记录。
//另外auto在codevs上会给编译警告,说在c++x11中含义可能不同,那就不用了吧
for (vector<timeFrame>::iterator now = f.begin(); now < f.end(); now++)
{
//若该时段有任一端位于已有的时段中,合并并从列表中取出。
if (tmp.begin >= (*now).begin && tmp.begin <= (*now).end || tmp.end >= (*now).begin && tmp.end <= (*now).end)
{
//合并时永远取的都是两者的最大值
tmp.begin = tmp.begin < (*now).begin ? tmp.begin : (*now).begin;
tmp.end = (*now).end > tmp.end ? (*now).end : tmp.end;
f.erase(now);
isCombined = true;
break;
}//另一种特殊情况是该时段完全覆盖一个已有时段,此时也做合并操作(无需计算)
else if (tmp.begin <= (*now).begin && tmp.end >= (*now).end)
{
f.erase(now);
isCombined = true;
break;
}
}
}
//当无可合并项时,就可以插入了。
f.push_back(tmp);
}
//读入时已经合并了所有可以被合并的时段,那么剩下的就一定无重叠的时段,只要排序然后很简单就可以计算出结果。
sort(f.begin(), f.end(), comparer);
int longestWork = f.front().end - f.front().begin;
int longestIdle = 0;
for (int i = f.size()-1; i > 0; i--)
{
if (f[i].end - f[i].begin > longestWork)
longestWork = f[i].end - f[i].begin;
if (f[i].begin - f[i - 1].end > longestIdle)
longestIdle = f[i].begin - f[i - 1].end;
}
printf("%d %d\n", longestWork, longestIdle);
return 0;
}