[USACO16JAN]愤怒的奶牛Angry Cows (单调队列优化dp)

题目链接

Solution

应该可以用二分拿部分分,时间 \(O(n^2logn)\)
然后可以考虑 \(n^2\) \(dp\) ,令 \(f_i\) 代表 \(i\) 点被激活,然后激活 \(i\) 之前所有点所需的半径。
那么很显然 \(f[i]=min(max(pos[i]-pos[j],f[j]))\) 其中 \(j<i\)
再从后往前记录一个 \(g[i]\) , 那么答案就为 \(min(max(f[i],g[i]))\)以及还要考虑两点中间的,其中 \(1<=i<=n\)
但是如果 \(n^2\) 处理解决不了 \(50000\) 的数据。
考虑优化。
观察到 \(f[j]\) 是递增的,而 \(pos[i]-pos[j]\) 是递减的。
那么只要是后面的 \(f[i]\) 比前面小的话,那么肯定他是最优解。
所以维护一个 \(f[i]\) 递增的单调队列即可。

Code

#include<bits/stdc++.h>
#define ll long long
#define N 50005
using namespace std;

ll n,a[N];

double pos[N],f[N],g[N];
void in(ll &x)
{
	ll f=1,w=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
	x=f*w; return;
}

int main()
{
	in(n);
	for(int i=1;i<=n;i++)
	{
		ll x; in(x);
		pos[i]=x*1.0;
	}
	sort(pos+1,pos+n+1);
	if(n==1){cout<<0<<endl;return 0;}
	if(n==2){cout<<pos[2]-pos[1]<<endl;return 0;}
	f[2]=pos[2]-pos[1];
	int head=1,tail=0;
	a[++tail]=2;
	for(int i=3;i<=n;i++)
	{
		while(max(pos[i]-pos[a[head]],f[a[head]]+1.0)>max(pos[i]-pos[a[head+1]],f[a[head+1]]+1.0))
		{if(head==tail)break;head++;}
		f[i]=max(pos[i]-pos[a[head]],f[a[head]]+1.0);
		while(f[i]<f[a[tail]]){tail--;if(tail<head)break;}
		a[++tail]=i;
	}
	g[n-1]=pos[n]-pos[n-1];
	memset(a,0,sizeof(a));
	head=tail=1;
	a[tail]=n-1;
	double ans=(0x3f3f3f3f3f)*1.0;
	for(int i=n-2;i>=1;i--)
	{
		while(max(pos[a[head]]-pos[i],g[a[head]]+1)>max(pos[a[head+1]]-pos[i],g[a[head+1]]+1))
		{if(head==tail)break;head++;}
		g[i]=max(pos[a[head]]-pos[i],g[a[head]]+1);
		while(g[i]<g[a[tail]]){tail--;if(tail<head)break;}
		a[++tail]=i;
	}
	for(int i=1;i<=n;i++)
	{
		ans=min(max(f[i]*1.0,g[i]*1.0),ans);
		if(i>1)
		ans=min(max((pos[i]-pos[i-1])*1.0/2,max(f[i-1]*1.0+1,g[i]*1.0+1)),ans);
	}
	printf("%.1lf",ans);
	return 0;
}
posted @ 2019-11-11 21:21  Kevin_naticl  阅读(277)  评论(0编辑  收藏  举报