【bzoj3203】[Sdoi2013]保护出题人 凸包+三分法

非常好的题目
山东二轮前几年还是不错的嘛
题解:http://www.cnblogs.com/iwtwiioi/p/4007263.html
前i个僵尸的血量和为sum[i]
那么第i关的攻击力就是
max{(sum[i]-sum[j-1])/(x[i]+i*d-j*d)}(1<=j<=i)
考虑一下为什么?
对于第i只僵尸,可以把前面i-1只僵尸的血量合到第i只上,取最大值已经保证了前i-1只僵尸符合条件

然后就是这个问题怎么快速求解
这个问题可以看做两个点之间的斜率
即所有(j*d,sum[j-1])与(x[i]+i*d,sum[i])之间斜率的最大值
显然这些能取到的点应该在下凸壳上
下凸壳上的点到这个询问的点的斜率是个单峰函数,所以可以三分

于是维护一个下凸壳,然后每次询问在下凸壳上三分即可

注意整数三分,需要判断到r-l<3时,对这几个数单独求解取最大


#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 100100

using namespace std;

struct yts
{
	double x,y;
}s[maxn];

double sum,d;
double ans;
int n,top;

yts operator-(yts x,yts y)
{
	yts ans;
	ans.x=x.x-y.x;ans.y=x.y-y.y;
	return ans;
}

double operator*(yts x,yts y)
{
	return x.x*y.y-x.y*y.x;
}

void insert(yts x)
{
	while (top>1 && (x-s[top])*(s[top]-s[top-1])>=0) top--;
	s[++top]=x;
}

double cal(yts a,yts b)
{
	return (b.y-a.y)/(b.x-a.x);
}

double max(double x,double y) {return x>y?x:y;}

double calc(yts x)
{
	int l=1,r=top;
	while (r-l>=3)
	{
		int mid=l+(r-l)/3,midmid=r-(r-l)/3;
		if (cal(s[mid],x)>cal(s[midmid],x)) r=midmid; else l=mid;
	}
	double ans=0.0;
	for (int i=l;i<=r;i++) ans=max(ans,cal(s[i],x));
	return ans;
}

int main()
{
	scanf("%d%lf",&n,&d);
	top=0;sum=0;ans=0;
	for (int i=1;i<=n;i++)
	{
		double x,y;
		scanf("%lf%lf",&x,&y);
		yts t;
		t.x=(double)i*d;t.y=sum;
		insert(t);
		sum+=x;
		t.x=y+(double)i*d;t.y=sum;
		ans+=calc(t);
	}
	printf("%.0lf\n",ans);
	return 0;
}


你可能感兴趣的:(【bzoj3203】[Sdoi2013]保护出题人 凸包+三分法)