P1485 火枪打怪

二分,对于能贡献到位置 (j) 的子弹,拆式子:

[sum p-(i_x-j)^2 ]

[sum p-i_x^2+2 imes i_x imes j-j^2 ]

[tot imes(p-j^2)-sum i_x^2+2 imes j imessum i_x ]

因为每次 check 贡献的范围是一样的,从后往前依次打死,差分维护最后定长区间中的子弹数量、威力值和、威力值平方和。

code:

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
#define Min(x,y)((x)<(y)?x:y)
#define For(i,x,y)for(i=x;i<=(y);i++)
#define Down(i,x,y)for(i=x;i>=(y);i--)
#define int long long
int x[N],y[N],z[N],m[N],n,k;
int read()
{
	int A;
	bool K;
	char C;
	C=A=K=0;
	while(C<'0'||C>'9')K|=C=='-',C=getchar();
	while(C>'/'&&C<':')A=(A<<3)+(A<<1)+(C^48),C=getchar();
	return(K?-A:A);
}
bool check(int p)
{
	int len=Min(sqrt(p),n),i,xx,yy,zz,tmp,res;
	xx=yy=zz=res=0;
	For(i,1,n<<1)x[i]=y[i]=z[i]=0;
	Down(i,n,1)
	{
		tmp=m[i]-(xx*(p-i*i)-yy+(i*zz<<1));
		xx-=x[i+len];
		yy-=y[i+len];
		zz-=z[i+len];
		if(tmp<0)continue;
		tmp=tmp/p+(tmp%p>0);
		res+=tmp;
		x[i]+=tmp;
		xx+=x[i];
		y[i]+=i*i*tmp;
		yy+=y[i];
		z[i]+=i*tmp;
		zz+=z[i];
		if(res>k)return 0;
	}
	return 1;
}
signed main()
{
	int i,l=1,r=10000000000000000ll,mid;
	n=read(),k=read();
	For(i,1,n)m[i]=read()+1;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid))r=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}
原文地址:https://www.cnblogs.com/May-2nd/p/14785237.html