Milking Cows 挤牛奶


描述

三个农民每天清晨5点起床,然后去牛棚给3头牛挤奶。第一个农民在300秒(从5点开始计时)给他的牛挤奶,一直到1000秒。第二个农民在700秒开始,在 1200秒结束。第三个农民在1500秒开始2100秒结束。期间最长的至少有一个农民在挤奶的连续时间为900秒(从300秒到1200秒),而最长的无人挤奶的连续时间(从挤奶开始一直到挤奶结束)为300秒(从1200秒到1500秒)。

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

  • 最长至少有一人在挤奶的时间段。
  • 最长的无人挤奶的时间段。(从有人挤奶开始算起)

PROGRAM NAME: milk2

INPUT FORMAT:

(file milk2.in)

Line 1:

一个整数N。

Lines 2..N+1:

每行两个小于1000000的非负整数,表示一个农民的开始时刻与结束时刻。

OUTPUT FORMAT:

(file milk2.out)

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


SAMPLE INPUT

3
300 1000
700 1200
1500 2100


SAMPLE OUTPUT

900 300
USACO/milk2

有四种思想分析

 

离散化

(其实就是进行了优化的搜索而已)

按照开始时间升序排序,然后从左到右扫一遍,复杂度是O(nlogn+n)的(排序+扫一遍,用堆、合并、快排都可以)。

所谓从左到右扫一遍,就是记录一个当前区间,[tmp_begin , tmp_end]

如果下一组数据的begin比tmp_end的小(或相等),则是连接起来的,检查这组数据的end,取max{end , tmp_end}。

如果下一组数据的begin比tmp_end的大,则是相互断开的,整理本区间,ans1取max{tmp_end - tmp_begin , ans1}。ans2取max{begin - tmp_end , ans2}

 

/* ID: 138_3531 PROG: milk2 LANG: C++ */ #include <iostream> #include <fstream> #include <algorithm> int MAX(int a,int b) {     return a>b?a:b; } using namespace std; struct Time {     int begin;     int end; }; bool cmp(const Time & a,const Time & b) {     return a.begin<b.begin; } int main() {     ofstream fout ("milk2.out");     ifstream fin ("milk2.in");     int n;     Time f[5001];     fin>>n;     for (int i=0;i<n;i++)        fin>>f[i].begin>>f[i].end;     sort(f,f+n,cmp);     int tmpbegin=f[0].begin,tmpend=f[0].end;     int ans1=tmpend-tmpbegin,ans2=0;     for (int i=1;i<n;i++)     {         if (f[i].begin<=tmpend)             tmpend=MAX(tmpend,f[i].end);         else         {             ans1=MAX(ans1,tmpend-tmpbegin);             ans2=MAX(ans2,f[i].begin-tmpend);             tmpbegin=f[i].begin;             tmpend=f[i].end;         }     }     fout<<ans1<<' '<<ans2<<endl;     return 0; }

 

++++++++++++++++++++++++++++

线段树

本题的规模是1e6,简单的模拟是O(nm)(n是奶牛个数,m是最大范围)的,会超时。(但是本题数据远没有描述的那么恐怖,直接模拟也是很快的}

用线段树统计区间,复杂度降为O(nlogm+m),可以接受。

 

怀疑是线段树的变形?

把每个输入看作是一个所谓的“线段”,只有开头和结尾。

既然每条线段有重叠现象,马上想出可以合并!按照开头和结尾从小到大排序,for一遍,如果发现前后两条线段开头与结尾重叠了,

(当然也要考虑被完全包括的情况),马上修改开头与结尾,并删除(可以标记),最后在剩下的线段中统计就可以了。

(好像非常易懂,也很容易实现)

 

标记数组(哈希)

1e6的范围,开一个布尔数组完全可以,有人为TRUE,无人为FALSE,注意边界即可。最后线性扫描即可。

时间复杂度,应该是O(n),n为最后结束的时间。

缺点就是……比较慢

+++++++++++本是同算法,相割何太急++++++++++++

和我的方法比较像,但我做了些优化

预处理:

 建立一个数组a。
 读入,若为起点将a[i]加1,若为终点将a[i]减1。
 (这时顺便找出总的起点与终点);

算法开始:

 将数组扫一遍(注意从总的起点扫到总的终点),这时将x(初始为0)加上a[i]。
  若遇到x由0变1,或由1变0,
  将这个点计入数组ans[]。
  然后再将ans扫描一遍,大家可能都想到了:
   若i为奇数,a[i+1]-a[i] 应该是有人的时间间隔;
   若i为偶数,反之。
这个算法是O(n),实际效果不错,但我也不知道应该叫什么。


叫什么好呢?并查集加速直接模拟

记录一个fa[i]表示i之后第一个没覆盖的点。 下次遇到这里的时候就可以直接跳过了。 复杂度大概算o(n)吧。

 


分段动规


消逝的分割线-------------------------
       时间复杂度(nlogn),全部0毫秒。 以开始时间从小到大快排每个农民

快排后:

f[i]表示第i个农民所在的最长连续线段 last_start表示这个线段的起点。 a[i].begin 第i个农民的起点时间,a[i].end 终点时间

可以得出方程: f[i]={max{f[i-1],a[i].end} (a[i].begin<=f[i-1]) //加上第i个农民仍然连续

     a[i].end             (a[i].begin>f[i-1])  /*加上第i个农民变不连续 
                                                 在这里开始以此农民的开始时间的新的一条连续线段,
                                                 更新last_start=a[i].begin作为新起点,因为有间隔,
                                                 所以更新longest_idle_time=max(longest_idle_time,a[i].begin-f[i-1]);*/

在每次循环处理后longest_continuous_time=max(longest_continuous_time,f[i]-last_start) 最后输出longest_continuous_time和longest_idle_time即可。

 
举杯独醉,饮罢飞雪,茫然又一年岁。 ------AbandonZHANG