题目链接:点击打开链接
题目大意:
有一些石块,你可以站在一个石块的右边向左边发射魔法球,对每个石块造成的伤害为当前魔法球的能量-(发射魔法球的位置-当前石块下标 i)的平方,具体看题目,下面说一下题目可能没有讲清楚的几点。
你可以理解为魔法球是具有穿透属性的,即对于每一个石块都会穿透且造成应有的伤害。
其次题目上也说了石块的血降为0后仍然在原地。
解题思路:
刚开始还以为是dp 贪心啥东西,其实仔细想想也算用了贪心。这题关键首先是二分找合适的答案,二分出来以后怎么check呢,
其实如果我们想破坏所有石块,就必须站在最右边开始发射魔法球,当前魔法球积累的能量如果超过了当前石块的血,那么就证明我们可以到下一个血量不为0的石块处开始发射魔法球。这样的过程如果暴力写的话应该是一个n^2的算法,所以我们要优化一下,其实就是对于每一个石块,统计一个当前所有已经发射的魔法球会对它造成的伤害,如果这个伤害不够,就在此处再继续释放魔法球直到所有魔法球造成的伤害合计高于石块血量。
具体的实现就是用一个队列存你已经发射的所有魔法球,然后每次将已经不能造成伤害的魔法球从队列中弹出,每次根据这些魔法球统计一个总伤害,至于这个统计是有一个式子的,我也讲不清楚,在此推荐一个学长的博客:点击打开链接,关于伤害的计算讲的很仔细。接下来就是模拟这个过程二分找到能满足条件的最小的解。以下贴代码,照着学长代码写的,感觉方法真的巧妙,
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <stack>
#include <set>
#include <functional>
#define retrun return
#define true ture
#define mian main
#define rank ra
#define next ne
#define pb push_back
#define hash haha
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const ll INF=(1ll<<60); //二分枚举的最大值
int n,k;
int a[50005];
int vs(ll m) //check过程
{
queue<int> que;
ll sum=0; //记录当前所有魔法球的会造成的伤害
ll dis=0; //记录当前可对石头造成影响的所有魔法球距它的距离和
ll ff=0; //记录当前可对石头造成影响的魔法球的个数
int res=0;
for(int i=n;i>=1;i--)
{
while(!que.empty()) //队列里面存的是当前所有可对石头造成伤害的魔法球
{
ll t=que.front();
if((t-i)*(t-i)<=m) //判断是否还能造成伤害
break;
sum-=(m-(t-i-1)*(t-i-1)); //不能造成伤害的话就将它从队列中去掉,并
dis-=(t-i-1); //在sum和dis中去掉它所造成的影响
ff--;
que.pop();
}
sum-=dis*2+ff; //更新sum和dis值
dis+=ff;
while(sum<a[i]+1) //判断当前总伤害值是否能超过当前石头血量
{ //若没有就在此处发射一颗魔法球
res++;
if(res>k)
return 0;
que.push(i);
sum+=m; //更新sum值
ff++;
}
}
return 1;
}
int main()
{
int QAQ;
scanf("%d",&QAQ);
while(QAQ--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
ll l=1,r=INF;
while(l<r) //二分找出符合要求的最小答案
{
ll mid=(l+r)/2;
if(vs(mid))
r=mid;
else
l=mid+1;
}
printf("%lld\n",r);
}
return 0;
}