解题报告 [codevs]1385 挤牛奶

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

解题思路

两种:

  1. 由于题目规定时间一共只有1000000时刻,即其实可以申请一百万个bool变量描述每一个时刻,读入一个时段时,将整个时段的bool置为true,最后只要简单的扫描最长的truefalse即可得出答案,编写比较简单,也很容易懂,但需要较多空间。

  2. 将每个时段都当做一段横线,即求最长的重叠线段,那么可以只记录时段的起始值,通过扫描将所有重叠的时段合并即可。最后排序一下,然后只要遍历一次就可以获得结果。

    无法合并的时段之间一定存在间隔,那么只要求间隔的最大值和剩余时段中的最大值即可得到答案,比第一种方法更省空间,在这种问题规模上效率不好说哪种方法更好,这一种方法时间复杂度不好求,因为合并本身可能产生新的可合并的时段。

由于个人有点空间紧张癖(滑稽),因此使用第二种方法。

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值