2.1 USACO Milking Cows

Milking Cows

Three farmers rise at 5 am each morning and head for the barn to milk three cows. The first farmer begins milking his cow at time 300 (measured in seconds after 5 am) and ends at time 1000. The second farmer begins at time 700 and ends at time 1200. The third farmer begins at time 1500 and ends at time 2100. The longest continuous time during which at least one farmer was milking a cow was 900 seconds (from 300 to 1200). The longest time no milking was done, between the beginning and the ending of all milking, was 300 seconds (1500 minus 1200).

Your job is to write a program that will examine a list of beginning and ending times for N (1 <= N <= 5000) farmers milking N cows and compute (in seconds):

  • The longest time interval at least one cow was milked.
  • The longest time interval (after milking starts) during which no cows were being milked.

PROGRAM NAME: milk2

INPUT FORMAT

Line 1: The single integer, N
Lines 2..N+1: Two non-negative integers less than 1,000,000, respectively the starting and ending time in seconds after 0500

SAMPLE INPUT (file milk2.in)

3
300 1000
700 1200
1500 2100

OUTPUT FORMAT

A single line with two integers that represent the longest continuous time of milking and the longest idle time.

SAMPLE OUTPUT (file milk2.out)

900 300

翻译:https://www.luogu.com.cn/problem/P1204

题目描述

三个农民每天清晨 55 点起床,然后去牛棚给三头牛挤奶。

第一个农民在 300300 秒 (从 55 点开始计时) 给他的牛挤奶,一直到 10001000 秒。第二个农民在 700700 秒开始,在 12001200 秒结束。第三个农民在 15001500 秒开始,21002100 秒结束。

期间最长的至少有一个农民在挤奶的连续时间为 900900 秒 (从 300300 秒到 12001200 秒),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为 300300 秒 (从 12001200 秒到 15001500 秒)。


你的任务是编一个程序,读入一个有 nn 个农民挤 nn 头牛的工作时间列表,计算以下两点(均以秒为单位):

最长至少有一人在挤奶的时间段。

最长的无人挤奶的时间段。(从有人挤奶开始算起)

输入格式

第一行一个正整数 nn

接下来 nn 行,每行两个非负整数 l,rl,r,表示一个农民的开始时刻与结束时刻。

输出格式

一行,两个整数,即题目所要求的两个答案。

输入输出样例

输入 #1复制

3
300 1000
700 1200
1500 2100

输出 #1复制

900 300

说明/提示

【数据范围】
对于 100\%100% 的数据,1\le n \le 50001≤n≤5000,1 \le l \le r \le 10^61≤l≤r≤106。

题目翻译来自NOCOW。

USACO Training Section 1.2

思路1:使用标记数组的思想,将挤奶的时间段设置为1,不挤奶的时间段设置为0.注意此题是从挤奶的时间开始算的。比如:

样例1:

1

5 6

这个样例的结果应该为1 0,而不是1,4

在进行标记的时候不能按照时间点标记。比如:

样例2:

2

1 2 

3 4

这个样例的结果应该为1,1,而不是4,0.

因此标记的时候可以定义一个标记数组a.a[i]=1表示i->i+1时间段在挤奶。a[i]=0,表示i->i+1时间段没有挤奶。

如样例2,此时a[2]=1,a[3]=0,a[4]=1.循环查找时也是从for 2...4

/*
ID: L
PROG: milk2
LANG: C++ 
*/ 
#include<iostream>
#define N 1000005
using namespace std;
int a[N];
int main()
{
	freopen("milk2.in","r",stdin);
	freopen("milk2.out","w",stdout);
	int n;
	cin >> n;
	int l,r,minl = N, maxr = 0;
	while(n--)
	{
		cin >> l >> r;
		for(int i = l; i < r; ++i)//将工作的区间段设为1,比如a[2]=1表示1-2s在工作 
		{
			a[i+1] = 1;
		 } 
		minl = min(minl,l);
		maxr = max(maxr,r);
	 } 
	int sum1 = 0, sum2 = 0, maxsum1 = 0, maxsum2 = 0;//sum1表示连续工作的时间和,maxsum1表示多个连续工作时间和里面的最大值 
	for(int i = minl+1; i <= maxr; ++i)//i要从minl+1开始循环,因为a[i]表示的时i->i+1时间在工作 
	{
		if(a[i] == 1) sum1++;
		if(a[i] == 0) sum2++;
		if(a[i] != a[i+1])
		{
			maxsum1 = max(maxsum1,sum1);
			maxsum2 = max(maxsum2,sum2);
			sum1 = sum2 = 0;//重置 
		}
	}
	cout << maxsum1 << " " << maxsum2 << endl; 
	return 0;
} 

使用差分和前缀和优化标记a数组的过程。

如样例1

2

1 2

2 5

cf[i]  cf[2]=1,cf[3]=-1,cf[3]=0,cf[6]=1

/*
ID: L
PROG: milk2
LANG: C++ 
*/ 
#include<iostream>
#define N 1000005
using namespace std;
int a[N];
int cf[N];
int main()
{
	//freopen("milk2.in","r",stdin);
	//freopen("milk2.out","w",stdout);
	int n;
	cin >> n;
	int l,r,minl = N, maxr = 0;
	while(n--)//使用差分数组优化标记挤奶时间段的过程 
	{
		cin >> l >> r;
		cf[l+1] += 1;
		cf[r+1] -= 1;
		minl = min(minl,l);
		maxr = max(maxr,r);
	}
	int sum1 = 0, sum2 = 0, maxsum1 = 0, maxsum2 = 0;
	for(int i = minl; i <= maxr; ++i)
	{
		cf[i] = cf[i-1] + cf[i];//恢复差分数组 ,cf[3]=2表示3-4这个时间段在工作,且有工作时间段重合了两次 
	//	cout << cf[i] << " ";
	}
//	cout << endl;
	for(int i = minl+1; i <= maxr; ++i)
	{
	//	cout << sum1 << " " << sum2 << endl;
		if(cf[i] >= 1) sum1++;
		if(cf[i] == 0) sum2++;
		maxsum1 = max(sum1,maxsum1);
		maxsum2 = max(sum2,maxsum2);
		if(cf[i] != cf[i+1] && (cf[i] == 0 || cf[i+1] == 0)) sum1 = sum2 = 0; 
	} 
	cout << maxsum1 << " " << maxsum2 << endl;
	return 0;
} 

解法3:使用区间相减的方法,这里直接粘贴下大佬的题解:https://www.luogu.com.cn/problemnew/solution/P1204

介绍一种本题的贪心解法。

本题要求读入一些挤牛奶的时间段,求最长至少有一人在挤牛奶的时间段和最长没有人在挤牛奶的时间段。把读入的区间视作线段,则题意转变为求至少有一条线段覆盖的最大区间和没有线段覆盖的区间

假设读入数据如下: fig

首先按照4条线段的起点位置排序(具体原因后面解释)。将begin设置为第一条线段的起点,将end设置为第一条线段的终点。

然后从第二条线段开始判断。如果该线段的起点小于end,则说明这两条线段有重合部分,将end更新为max{end,该线段的终点位置}。如果该线段的起点大于end,则说明该线段及以后的线段再也不会与前面的线段产生任何重合部分(这也就是排序的作用),那么可以更新ans1和ans2的值:ans1更新为max{ans1,end-begin},ans2更新为max{ans2,该线段的起点位置-end}。具体参见图中第4条线段,ans1被更新为1200-0,ans2被更新为1400-1200。

程序已经基本成型,但要注意在输出答案前更新一遍ans1的值,这是为了避免所有线段均有重合部分而无法判断的情况。另外,ans1和ans2要初始化为0。

#include<iostream>
#include<algorithm> 
using namespace std;
struct t{
	int begin;
	int end;
};
t a[5005];
bool cmp(t x, t y)//按照开始时间排序 
{
	if(x.begin <= y.begin) return true;
	else return false;
}
int main()
{
	int n;
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> a[i].begin >> a[i].end;
	sort(a+1,a+1+n,cmp);
	//for(int i = 1; i <= n; ++i) cout << a[i].start << " " << a[i].end << endl;
	int begin = a[1].begin;
	int end = a[1].end;
	int ans1 = 0, ans2 = 0;
	for(int i = 2; i <= n; ++i) 
	{
		if(a[i].begin <= end)//时间有重合,比较第二次开始时间和第一次结束时间 
			end = max(end,a[i].end);
		else//时间没有重合 
		{
			ans1 = max(ans1,end-begin);
			ans2 = max(ans2,a[i].begin-end);
			begin = a[i].begin;
			end = a[i].end;
		}
	}
	ans1 = max(ans1,end-begin);//只有一个样例的情况,比如1  5 6,答案为1
	cout << ans1 << " " << ans2 << endl;
	return 0;
} 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章