「高手训练」手机游戏(monster)

前言

坑点还是蛮多的,不过既然自己做出来了就总结一下吧!


题目描述

明明的手机上有这样一个游戏,一排 n n n个怪物,每个怪物的血量是 m i m_{i} mi
现在明明可以射出 k k k个伤害均为 p p p的火球,当某个火球射到第 i i i个怪物,除了这个怪物会掉血以外,它左边的第 j个怪物 ( j j j ≤ \leq i i i),也会遭到 m a x ( 0 , p − ( i − j ) 2 ) max(0,p-(i-j)^2) max(0,p(ij)2)的溅射伤害。
当某个怪物的血量为负的时候,它就死了,但它的尸体依然存在,即其他怪物不会因为它死而改变位置。
明明想用这 k个火球消灭掉所有的怪物,但他同时希望每个火球的伤害 能尽可能的小,这样他才能完美过关。
所有数均为整数。
输入格式
第一行两个数 n n n, k k k
第二行 n n n个数 表示每个怪物的生命值 m i m_i mi
输出格式
一行一个整数表示最小的符合要求的 p p p值。
样例
输入样例1
3 1
1 4 5
输出样例1
6
数据范围与提示
对于 30 30 30%的数据, n n n ≤ \leq 300 300 300
对于 100 100 100%的数据, n n n , k k k ≤ \leq 5 e 5 5e5 5e5, m i m_i mi ≤ \leq 1 e 10 1e10 1e10
其中
内存限制:128 MiB
时间限制:1000 ms

网上的已有题解很多是 n 2 log ⁡ max ⁡ m i n^2 \log \max m_i n2logmaxmi假做法,因为一本通的OJ数据太水所以能过,但很明显这个时间复杂度是不对的。

显然火球大小对于能否通关来说是具有单调性的,所以考虑二分答案。
下界必定为1,上界的话考虑 k k k最小, m i m_i mi最大,则只通过溅射伤害能够打到第一个怪物,需要10000000000+(499999)^2,即2e11左右。

但是这样做, n log ⁡ n n \log n nlogn的时间复杂度只有500000*40=2e7左右,似乎对于1s的时限有点小,那么肯定需要在 c h e c k 函 数 check函数 check上做文章。

首先,显而易见的是我们每次需要从最后一个怪物开始打。

Key Point 1

其次,每次暴力枚举 p p p n 2 n^2 n2的做法,那么我们考虑把每个数被操作后对左边的贡献是什么。

拆分一下题目中的 p p p- ( i − j ) 2 (i-j)^2 (ij)2,即 p p p- i 2 i^2 i2- j 2 j^2 j2+ 2 i j 2ij 2ij,我们把 i 2 i^2 i2的和存下来,称作Sumi,这在后面做的贡献肯定是一定的。
i i i的值存下来,称作Sum。
由乘法分配律, 2 ( i + k ) j 2(i+k)j 2(i+k)j= 2 i j 2ij 2ij+ 2 k j 2kj 2kj,也就是说 ∑ i = j + 1 且 做 献 n i ∗ j \sum_{i=j+1且做献}^ni*j i=j+1nij=Sum*j
j 2 j^2 j2在遍历时处理即可。

Key Point 2

但是这样交上去明显不对,因为我们还没有计算被减去的贡献值,即,当 p − ( i − j ) 2 p-(i-j)^2 p(ij)2 ≤ \leq 0 0 0时,它所做的贡献全部被消除,后面不再计算

Solution 1直接遍历时处理,但我们没办法存下每一处做贡献的i,这样做也是 n 2 n^2 n2

还记得上面那句话吗

似乎对于1s的时限有点小。

我们惊喜的发现,贡献被抵消的位置也是具有单调性的,即如果 p − ( i − j ) 2 p-(i-j)^2 p(ij)2 ≤ \leq 0 0 0,那么对于 k k k < < < j j j , p − ( i − k ) 2 p-(i-k)^2 p(ik)2必定小于 0 0 0
那么我们可以再套一个二分,求出第一次贡献消除的位置。
Code

int search(int l,int r,int val,int op)//op指当前位置,val指外面一层二分的值
{
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if((op-mid)*(op-mid)>=val)
		{
			l=mid;//超过了,枚举更近的位置
		}
		else{
			r=mid-1;
		}
	}
	return l;
}

这样做,整体时间复杂度就是 n   l o g n log ⁡ max ⁡ m i n\ log n \log \max m_i n lognlogmaxmi ≈ \approx 4 e 8 4e8 4e8,但是数据是跑不满的,所以
开个c++11差不多是能过的

完整代码

#include <cstdio>
#include <iostream>
#include<cstring>
using namespace std;
#define int long long
int n, k, a[500005], r = 5e11, l = 1;
int Sumi, Sum, kk, sp,op,val[500005],S[500005],I[500005],cnt[500005];
bool pd;
int search(int l,int r,int val,int op)
{
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if((op-mid)*(op-mid)>=val)
		{
			l=mid;
		}
		else{
			r=mid-1;
		}
	}
	return l;
}
bool check(int now) {
    Sumi = 0, Sum = 0, kk = 0, sp = 0,op=0;//kk指用的火球个数,这样的好处是不会出现减去消除值后出现负数
    pd = 1;
    int X = 0;
    memset(val,0,sizeof(val));
    memset(S,0,sizeof(S));
    memset(I,0,sizeof(I));
    memset(cnt,0,sizeof(cnt));
    for (int i = n; i >= 1; i--) {
        X = a[i];
        sp-=val[i];//消除贡献
        Sumi-=S[i];
        Sum-=I[i];
        op+=cnt[i];
		X -=  sp - (Sumi +(kk-op) *i * i - 2 * Sum * i);//因为最多把前面所有贡献减完,所以最小为0
        if (X < 0) {
            continue;
        }
        int p = X / now;
        if (now * p <= X ) {
            p++;
        }
        if (kk + p > k) {
            pd = 0;
            break;
        }
        sp += p * now;//增加的p值
        kk += p;//使用数量
        Sumi += p * i * i;//存储i^2
        Sum += p * i;//存储i
        int kp=search(0,i-1,now,i);//第一次消除贡献的位置
        val[kp]+=p*now;//达到这个位置,贡献被消除
        S[kp]+=p * i * i;
        I[kp]+=p*i;
        cnt[kp]+=p;
    }
    return pd;
}
signed main() {
    scanf("%lld%lld", &n, &k);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
    }
    int ans=0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid)) {
        	ans=mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    printf("%lld", ans);
    return 0;
}

提交记录:

End

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值