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


半官方题解:

有四种思想

[编辑]离散化

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

按照开始时间升序排序,然后从左到右扫一遍,复杂度是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}


我觉得其实是不用排序的

设bi,ei分别为第i个输入inc(map[bi]);dec(map[ei])

记录下min_x和max_y,从min_x到max~y-1扫描,分别统计最大的连续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为奇数,ans[i+1]-ans[i] 应该是有人的时间间隔;

若i为偶数,反之。

这个算法是O(n),实际效果不错,但我也不知道应该叫什么。

回楼上的话,这种方法可以称之为是差分。见noip2012提高组 借教室。具体指有一列数字a,

再找来一个数组s记录相邻数字之差,这样每个数字a[i]都是s[1]+s[2]+...+s[i-1]。对于一

段数字的加减法就可以通过将这一段开头的差加上相应的数字,再把这一段结尾的数字减去相

应的数值即可。很巧妙的方法。

[编辑]叫什么好呢?并查集加速直接模拟

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

[编辑]分段动规

消逝者--183.16.23.147 22:26 2011年11月3日 (CST)

时间复杂度(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即可。


在我看来,不就是排个序,然后跑下标,判断开始和结束的时间,然后O(n)跑一遍不就完了吗?然而就是搞不定啊,邪门啊,最多过两组示例,然后发现右端点要记录最大值,还是乖乖写篇博客,记录一下这坑人的一题,WA两天,折磨。

代码实现:

/*
  ID : ever g1
  PROG : milk2
  LANG : C++
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<cstdio>
#define ll long long
#define mset(a,x) memset(a,x,sizeof(a))

using namespace std;
const double PI=acos(-1);
const int inf=0x3f3f3f3f;
const double esp=1e-6;
const int maxn=100005;
const int mod=1e9+7;
int dir[4][2]={0,1,1,0,0,-1,-1,0};
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;}
ll inv(ll b){if(b==1)return 1; return (mod-mod/b)*inv(mod%b)%mod;}
ll fpow(ll n,ll k){ll r=1;for(;k;k>>=1){if(k&1)r=r*n%mod;n=n*n%mod;}return r;}
struct node{
	int l,r;
}p[5005];

int cmp(node a,node b)
{
	if(a.l==b.l)
	return a.r<b.r;
	
	return a.l<b.l; 
}

int main()
{
	freopen("milk2.in","r",stdin);
	freopen("milk2.out","w",stdout);
    int n,i,j,k,minn,maxx,visit[5005];
    cin>>n;
    for(i=0;i<n;i++)
    cin>>p[i].l>>p[i].r;
    
    sort(p,p+n,cmp);
    int l=p[0].l,r=p[0].r;
    if(n==1)
    {
    	cout<<p[0].r-p[0].l<<' '<<"0"<<endl;
    	
    	goto q;
	}
    
    i=1;minn=maxx=0;
    while(i<n)
    {
    	while(i<n&&p[i].l<=r)
    	{
    		if(p[i].r>r)
    		r=p[i].r;
    		
    		i++;
		}
		if(r-l>maxx)
		maxx=r-l;
		if(i==n)
		break;
		if(p[i].l-r>minn)
		minn=p[i].l-r;
		
		l=p[i].l;r=p[i].r;
		i++;
	}
    cout<<maxx<<' '<<minn<<endl;
q:	return 0;
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值