题解 P3299 保护出题人 (斜率转化凸包+三分)

该博客详细介绍了如何解决P3299问题,涉及模拟植物大战僵尸的情景,通过转化为斜率计算,利用凸包理论找到最大斜率,并通过三分法寻找关键点,从而求出最小攻击力。博客提供了问题的数学表达式和证明过程,以及最终的时间复杂度分析。
题目源自洛谷
题目大意:

模拟植物大战僵尸的一排,可以理解成豌豆射手对第一个僵尸发射激光,造成持续伤害并且第一个僵尸死后第二个僵尸立马收到伤害。
然后不同的是,有n个回合,每回合在僵尸队列前端会添加一个僵尸,之后的僵尸会往后移动d个距离,n个回合单独计算。求n个回合杀死所有僵尸的最小攻击力(hp/s)。

题目讲解:

首先模拟一下样例,贪心的求:在第i个回合,能杀死前j个僵尸的攻击力,取最大值;数学表达就是 m a x ( s u m i − s u m j − 1 x i + ( i − j ) ∗ d ) max(\frac{sum_i-sum_{j-1}}{x_i+(i-j)*d}) max(xi+(ij)dsumisumj1)
得到公式后,很显然,可以转化为两点求斜率的式子:
s u m i − s u m j − 1 x i + i ∗ d − j ∗ d \frac{sum_i-sum_{j-1}} {x_i+i*d-j*d} xi+idjdsumisumj1
即点 ( x i + i ∗ d , s u m i ) (x_i+i*d,sum_i) (xi+id,sumi) ( j ∗ d , s u m j − 1 ) (j*d,sum_{j-1}) (jd,sumj1)
可以发现对于每回合,第一个点是唯一的,第二类点的个数是随i增大而增多的,但也都是固定的。
这时候此题已经转化为了对于每回合,求第一个点与前i个第2类点连成线段斜率的最大值。
看图我们可以大致猜测一个结论:斜率最大值的点一定在第二类点构成的下凸壳上;
之后我们来简单证明(感谢同校大佬提供的思路,%%%Daowuu):
如图是i=5的时候的第一种点(E) 和第二类点(A、B、C、D),尝试将E也加入凸包中,假设与E相连的点为P,那么与E连线斜率最大的点一定是点P。因为根据凸包的性质可知其他点一定在与PE这条边的内侧,即其他点与E连线的斜率一定小于PE的斜率,证毕。
而此时,凸壳上的点会被分成两部分,一部分是在E加入凸壳后仍然在凸壳边上的点,这些点(x坐标小于P点)的斜率一定小于E,而E加入后可能会弹出一部分点(x坐标大于P点),这些点的斜率也是小于PE的,此时我们会发现在原凸壳上,与E连线的斜率是一个单峰函数。
最后采用三分寻找P点即可。
时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
保护出题人
代码:

#include <bits/stdc++.h>
#define int long long
#define eps 0.00000001
using namespace std;

struct Point{
	double x,y;
	Point(double x=0,double y=0):x(x),y(y){}
};
Point stk[100005];
typedef Point Vector;

Vector operator + (Vector A, Vector B) {return Vector(A.x+B.x,A.y+B.y);}
Vector operator - (Vector A, Vector B) {return Vector(A.x-B.x,A.y-B.y);}
Vector operator * (Vector A, double B) {return Vector(A.x*B,A.y*B);}
Vector operator / (Vector A, double B) {return Vector(A.x/B,A.y/B);}

int dcmp(double x)
{
	if(fabs(x)<eps) return 0;
	else if(x>0) return 1;
	else return -1;
}

double cross(Vector p,Vector q){return p.x*q.y-p.y*q.x;}
int n,d,cnt=0;
double hp,pos,pre[100005];

signed main()
{
	scanf("%lld%lld",&n,&d);
	double res=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf",&hp,&pos);
		pre[i]=pre[i-1]+hp;
		Point p={i*1.0*d,pre[i-1]},now={pos+i*1.0*d,pre[i]};

		while(cnt>1&&dcmp(cross(stk[cnt]-stk[cnt-1],p-stk[cnt]))<0) cnt--;
		cnt++;
		stk[cnt]=p;

		if(cnt==1){
			res+=(hp/pos);
		}else{
			int left=1,right=cnt;
			while(left+10<right)
			{
				int mid=(left+right)/2;
				int midr=(mid+right)/2;
				double k1=(now.y-stk[mid ].y)/(now.x-stk[mid ].x);
				double k2=(now.y-stk[midr].y)/(now.x-stk[midr].x);

				if(k1>k2) right=midr;
				else if(k1<k2) left=mid;
			}

			double maxd=0;
			for(int j=left;j<=right;j++)
			{
				double temp=(now.y-stk[j].y)/(now.x-stk[j].x);
				maxd=max(maxd,temp);
			}
			res+=maxd;
		}
	}
	printf("%.0lf\n",res);
	return 0;
}
更新于2021/10/21
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值