Rescue
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 975 Accepted Submission(s): 260
2 1 1 1 3 1 1 4 5
2 6
正常想法是从后向前扫,记录每个点已经被打的能量,这样是不好通过后面的已经确定的一些值来快速确定的
所以就记录我这个点被打的损耗的能量,也就是(j-i)*(j-i),在记录一个右边打到了这个点多少次
那么这个点已经被打的能量也就是 总次数乘以p减去损耗,设为t
设每个点原本的能量num【i】
我在当前这个点还需要打的次数也就是
(num【i】-t)/p+1(加一是因为题意“larger than”)
现在还需要解决的是怎么快速的通过右边的值来确定 当前这个点的 被打的损耗的能量(真的很巧妙。。。)
我们设能打到 当前点(index)右边第一个点(index+1) 的所有气功波起始点为j1,j2,j3,j4,。。jk
再设xi= ji - index -1,(i=1->k)index+1这个点的所有损耗也就是sum( xi*xi ),我们把这个值记录下来,再记录下来sum(xi),和k
再转移到index时,先把那些打不到index这个点的气功波都去掉 ,这个完全可以记录一个下标,while循环一些On就可以解决
去完之后k的值还有sum( xi*xi )sum(xi)都是有变化的,相当于是记录了能打到index这个点的气功波在对index+1这个点的时的消耗
而对index的消耗,其实只是所有xi的值都加了1
这样损耗也就是sum((xi+1)*(xi+1)) = sum(xi*xi+2*xi+1)=sum(xi*xi)+2*sum(xi)+k
之前记录的值就派上用场了
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=50005;
ll num[maxn],cnt[maxn];
int k,n;
int check(ll x)
{
ll sum2=0,sum1=0,sum=0,ans=0;
int j=n-1,i;
for(i=n-1;i>=0;i--)
{
if(j>i)
{
while((j-i)*(j-i)>=x)
{
sum2-=cnt[j]*(j-i-1)*(j-i-1);
sum1-=cnt[j]*(j-i-1);
sum-=cnt[j];
j--;
}
}
sum2+=2*sum1+sum;
sum1+=sum;
ll t=num[i]-sum*x+sum2;
if(t<0)
cnt[i]=0;
else
cnt[i]=t/x+1;
ans+=cnt[i];
sum+=cnt[i];
}
return ans<=k;
}
int main()
{
int T;
int i;
cin>>T;
while(T--)
{
cin>>n>>k;
for(i=0;i<n;i++)
scanf("%d",&num[i]);
ll le=1,ri=1e12,mid,ans;
while(le<=ri)
{
mid=(le+ri)>>1;
if(check(mid))
{
ri=mid-1;
ans=mid;
}
else
le=mid+1;
}
cout<<ans<<endl;
}
return 0;
}