[BZOJ3203] [SDOI2013]保护出题人(二分+凸包)

[BZOJ3203] [SDOI2013]保护出题人(二分+凸包)

题面

题面较长,略

分析

对于第i关,我们算出能够打死前k个个僵尸的最小能力值,再取最大值就可以得到\(y_i\).

前j-1个僵尸到门的距离为\(x_i+(i-j+1) \times d\),血量为\(sum[i]-sum[j]\),因此

\[y_i=max(\frac{sum_i-sum_j}{x_i+(i-j+1) \times d})= max(\frac{sum_i-sum_j}{x_i+i \times d-(j+1)\times d}) \]

这个方程没法用一般的斜率优化来转移,但我们注意到除法很像斜率的形式,实际上是\((x_i+i \times d,sum_i)\)\(((j+1)\times d,sum_j)\)之间的斜率

那么问题就转化成,给出一个定点\((x_i+i \times d,sum_i)\),求一个点\(((j+1)\times d,sum_j)\),使得这两点间斜率最大

显然备选的点应该在一个斜率单调递增凸壳上。那么我们可以像斜率优化那样维护一个凸壳。找斜率最大点显然可以三分,但还有更简单的方法。注意到斜率最大点下方的点到定点的向量,和凸壳上相邻点的向量的叉积为负。我们只要找到x坐标最小,且叉积为正的点即可,直接二分答案。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 100000
#define eps 1e-6
using namespace std;
int n;
double d;
struct Vector{
	double x;
	double y;
	Vector(){
		
	}
	Vector(double _x,double _y){
		x=_x;
		y=_y;
	}
	friend Vector operator + (Vector p,Vector q){
		return Vector(p.x+q.x,p.y+q.y);
	}
	friend Vector operator - (Vector p,Vector q){
		return Vector(p.x-q.x,p.y-q.y);
	}
};
typedef Vector point;
double cross(Vector p,Vector q){
	return p.x*q.y-p.y*q.x;
}
double slope(point p,point q){
	return (p.y-q.y)/(p.x-q.x);
}

double a[maxn+5],x[maxn+5]; 
double suma[maxn+5];
point s[maxn+5];

int bin_search(int l,int r,point k){
	int ans=1;
	int mid;
	while(l<=r){
		mid=(l+r)>>1;
		if(cross(s[mid+1]-s[mid],k-s[mid])<=eps){
			ans=mid;
			r=mid-1;
		}else l=mid+1;
	}
	return ans;
}
int main(){
	scanf("%d %lf",&n,&d);
	for(int i=1;i<=n;i++){
		 scanf("%lf %lf",&a[i],&x[i]);
		 suma[i]=suma[i-1]+a[i];
	}
	int top=0;
	double ans=0;
	s[++top]=point(d,0); //((0+1)*d,suma[0]) 
	for(int i=1;i<=n;i++){
		int best=bin_search(1,top,point(x[i]+i*d,suma[i]));//找到斜率最大的点
		double val=slope(point(x[i]+i*d,suma[i]),s[best]); 
		ans+=val;
		point newp=point((i+1)*d,suma[i]);//插入新的可行点
		while(top>1&&cross(s[top]-s[top-1],newp-s[top-1])<=eps) top--;
		s[++top]=newp;
	}
	printf("%.0f\n",ans);
}

posted @ 2019-08-19 13:15  birchtree  阅读(168)  评论(0编辑  收藏  举报