USACO Section 1.2 Milking Cows 解题报告

题目

题目描述

有3个农夫每天早上五点钟便起床去挤牛奶,现在第一个农夫挤牛奶的时刻为300(五点钟之后的第300个分钟开始),1000的时候结束。第二个农夫从700开始,1200结束。最后一个农夫从1500开始,2100结束。现在我们可以算出,在一段时间内至少有一个农夫在持续挤牛奶的最长时间间隔为900分钟(1200-300),在他们挤牛奶的过程中,没有人挤牛奶的最长时间间隔为300分钟(1500-1200)。

现在有N个农夫,给出他们每个人挤牛奶的开始时刻与结束时刻。分别计算出在一段时间内至少有一个农夫在持续挤牛奶的最长时间间隔,与没有人挤牛奶的最长时间间隔。

数据范围

  1. 1 <= N <= 5000
  2. 农夫们挤牛奶的开始时刻与结束时刻都小于1000000

样例输入

3
300 1000
700 1200
1500 2100

样例输出

900 300

解题思路

首先按照农夫们的开始时刻进行从小到大排序,然后利用线段覆盖原理,用标记将时间段的开始时刻与结束时刻标好,两个时间段重叠在一起之后就可以看作是一个时间段,这时就应该更新标记。当出现时间段不连续的时候,我们就需要开始更新没有人挤牛奶的开始时刻与结束时刻了。每次更新时间段的标记,我们都应该计算出时间间隔,最终取最大的时间间隔。

排序的时间复杂度为O(NlogN),下面的处理时间间隔操作时间复杂度为O(N),所以这种算法的时间复杂度为O(NlogN)

解题代码

/*
ID: yinzong2
PROG: milk2
LANG: C++11
*/
#define MARK
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath> using namespace std;
const int maxn = 5000+10; int n;
//flagBegin1与flagEnd1表示long milking periods的起始时刻与结束时刻
//flagBegin2与flagEnd2表示long non-milking periods的起始时刻与结束时刻
int flagBegin1, flagEnd1, flagBegin2, flagEnd2;
int _max1, _max2; struct TimeSeg {
int b, e;
}t[maxn]; bool cmp(TimeSeg x, TimeSeg y) {
if(x.b < y.b) {
return true;
} else if(x.b == y.b) {
return x.e < y.e;
}
return false;
} void work() {
flagBegin1 = t[0].b;
flagEnd1 = t[0].e;
_max1 = flagEnd1-flagBegin1; flagBegin2 = flagEnd2 = t[0].e; for(int i = 1; i < n; i++) {
if(t[i].b <= flagEnd1) {
if(t[i].e > flagEnd1) {
flagEnd1 = t[i].e;
flagBegin2 = flagEnd2 = t[i].e;
}
} else {
_max1 = max(_max1, flagEnd1-flagBegin1);
flagBegin1 = t[i].b;
flagEnd1 = t[i].e; flagEnd2 = t[i].b;
_max2 = max(_max2, flagEnd2-flagBegin2);
flagBegin2 = flagEnd2 = t[i].e;
}
}
printf("%d %d\n", _max1, _max2);
} int main() {
#ifdef MARK
freopen("milk2.in", "r", stdin);
freopen("milk2.out", "w", stdout);
#endif // MARK
while(~scanf("%d", &n)) {
for(int i = 0; i < n; i++) {
scanf("%d%d", &t[i].b, &t[i].e);
}
sort(t, t+n, cmp);
_max1 = _max2 = 0;
work();
}
return 0;
}

解题思路(Type 2)

我们可以用最朴素的办法,将时间复杂度优化为O(N),因为数据量比较小,所以我们可以直接开一个bool类型的数组,来记录挤牛奶的时间,在某一时刻有人挤牛奶那么就赋值为true,否则为false。最后线性的扫描一遍取得最大的时间间隔。

这种方法最关键的地方就是边界的处理,一定要细心。注意题目给的是农夫们挤牛奶的时刻,但是我们要计算的是挤牛奶的时间间隔。例如:某位农夫从1时刻一直到5时刻都在挤牛奶,我们在bool数组里面从1到5都会赋值为true,这样数出来为5,其实时间间隔为4,因为我们要将时刻变为时间间隔,要减去1。

所以我们在赋值的时候需要稍微处理一下,当农夫挤牛奶的时刻为A到B时,我们在数组中记录应该从A+1~B都赋为true,这样便于下面的操作。

解题代码(Type 2)

/*
ID: yinzong2
PROG: milk2
LANG: C++11
*/
#define MARK
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm> using namespace std;
const int maxn = 1e6 + 10; int n;
//申明变量的时候一定不能用C++里面的关键字,例如time[],这样的变量在某些oj有可能会报错
bool _time[maxn]; int main() {
#ifdef MARK
freopen("milk2.in", "r", stdin);
freopen("milk2.out", "w", stdout);
#endif
while(~scanf("%d", &n)) {
memset(_time, false, sizeof(_time));
int b, e;
int first = maxn;
int last = -1;
for(int i = 0; i < n; i++) {
scanf("%d%d", &b, &e);
first = min(first, b+1);
last = max(last, e); //注意边界,我们记录的是时刻,但是要计算的是时间间隔
for(int j = b+1; j <= e; j++) {
_time[j] = true;
}
} bool flag = true;
int cnt1 = 0, cnt2 = 0;
int _max1 = 0, _max2 = 0;
for(int i = first; i <= last; i++) {
if(_time[i] != flag) {
if(flag) {
_max1 = max(_max1, cnt1);
} else {
_max2 = max(_max2, cnt2);
}
cnt1 = cnt2 = 0;
flag = _time[i];
//回退一次,重新开始计数
i--;
} else {
if(flag) cnt1++;
else cnt2++;
}
}
//最后的last一定是true,我们上面的循环没有将最后一段时间间隔进行比较,所以这里需要补充
_max1 = max(_max1, cnt1);
printf("%d %d\n", _max1, _max2);
}
return 0;
}
上一篇:Markdown 语法详尽笔记大全 2019


下一篇:集中式日志分析平台 - ELK Stack - 安全解决方案 X-Pack