欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[二分+DP]魔棒

程序员文章站 2022-05-14 21:08:28
...

希望更好的阅读效果?点这里

题目:

描述

有一个英雄,初始生命值是 hphp(生命值无上限),在接下来的 nnn500n \le 500)秒内,每秒会受到一次伤害,第 ii 秒受到的伤害值为 a[i]a[i]1a[i]10001 \le |a[i]| \le 1000)。这个英雄有一个道具"魔杖",魔杖的初始能量为 00 ,每受到 11 次伤害,积攒 11 点能量。在英雄受到伤害后,可以立即释放魔棒中的能量,恢复 15×[]15 \times [能量点数] 的生命值,且魔棒的点数清零。释放能量有施法间隔 cdcdcdcd 是正整数),即相邻的两次释放的时间间隔至少有 cdcd 秒。任何时刻当 hp0hp \le 0 时视为死亡,问这个英雄存活下来的前提下,cdcd 的值最大可以是多少?注意,若 a[i]a[i] 为负,受到"伤害"后实际上生命值是增加的,魔棒仍然积攒能量。

输入

第一行一个整数 TT ,表示测试数据的组数。对于每组测试数据:
11 行两个正整数 n,hpn,hp ,含义如题目所述。
22nn 个整数,分别是 a[1]a[n]a[1] \sim a[n]

输出

对每组测试数据输出一行包含一个数,即最大的 cdcdcdcd 是一个正整数。如果 cdcd 没有上限,输出"No upper bound."(不含双引号);如果无论如何都不能存活,输出 1−1

分析:

多组数据 T5T \le 5n500n \le 500 是很小的,但是真的有那么容易吗?

cdcd 最大为 xx ,如果 cd>xcd>x 英雄不能存活,否则就能够存活。于是可以二分 cdcd 的值,如果 1 n1~nhp>0hp>0 ,则把 midmid 调大。否则就调小。

像我这种蒟蒻差分约束系统显然是不会的,只会用DP写check函数。

f[i][j][k]f[i][j][k] 表示到第 ii 秒末,能量点数为 jj ,释放了 kk 次后还有的最大生命值。

初始化:f[0][0][0]=hpf[0][0][0]=hp

因为释放次数大于等于 22 时需要知道间隔,所以k取 0,1,20,1,2 三个值就行了。(k=2k=2 时表示释放次数 2\ge 2),对每个取值分类讨论:

  • k=0:k=0:

    此时很明显 j=ij=i ,所以:

    f[i][i][0]=f[i1][i1][0]a[i]f[i][i][0]=f[i-1][i-1][0]-a[i]

    注意要满足 f[i1][i1][0]>a[i]f[i-1][i-1][0]>a[i] ,表示能活到第 ii 秒。

  • k=1:k=1:

    1. 在第 ii 秒末释放能量:

      f[i][0][1]=f[i1][i1][0]a[i]+i×15 f[i][0][1]=f[i-1][i-1][0]-a[i]+i \times 15

      这时也要满足 f[i1][i1][0]>a[i]f[i-1][i-1][0]>a[i]

    2. 在前面释放能量:

      jj11i1i-1 (因为已经释放过一次,所以 jj 不能等于 ii):

      f[i][j][1]=max{f[i1][j1][1]a[i]} f[i][j][1]=max \{f[i-1][j-1][1]-a[i] \}

      这时要满足 f[i1][j1][1]>a[i]f[i-1][j-1][1]>a[i]

  • k=2:k=2:

    1. 前面释放过但第 ii 秒末没有释放:

      f[i][j][2]=max{f[i1][j1][2]a[i]} f[i][j][2]=max \{f[i-1][j-1][2]-a[i] \}

      此时要满足 f[i1][j1][2]>a[i]f[i-1][j-1][2]>a[i]

    2. ii 秒末释放的是第二次:

      f[i][0][2]=max{f[i][j][2],f[i1][j1][1]a[i]+j×15} f[i][0][2]=max \{f[i][j][2],f[i-1][j-1][1]-a[i]+j \times 15 \}

      f[i1][j1][1]a[i]+j×15f[i-1][j-1][1]-a[i]+j \times 15”表示 i1i-1 秒末,能量为 j1j-1 时或在前面放过一次,此时要满足 jmidj \ge mid 并且 f[i1][j1][1]>a[i]f[i-1][j-1][1]>a[i]

    3. ii 秒末释放第 kk 次(k>2k>2):

      f[i][0][2]=max{f[i][j][2],f[i1][j1][2]a[i]+j×15} f[i][0][2]=max \{f[i][j][2],f[i-1][j-1][2]-a[i]+j \times 15 \}

      这个式子表示 i1i-1 秒末,能量为 j1j-1 时或在前面放过多次,此时也要满足 jmidj \ge midf[i1][j1][2]>a[i]f[i-1][j-1][2]>a[i]

最后判断 f[n][i][0],f[n][i][1],f[n][i][2]f[n][i][0],f[n][i][1],f[n][i][2] ,只要有一个大于 00 就行了。

代码:

#include <cstdio>
#include <cstring>
#define max(a,b) ((a)>(b)?(a):(b))
int t,n,hp,a[503],f[503][503][3];
bool check(int mid){
    memset(f,-0x3f,sizeof f);//多组数据一定要清空
    f[0][0][0]=hp;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++){
            //k=0:
            if(f[i-1][i-1][0]>a[i]) f[i][i][0]=f[i-1][i-1][0]-a[i];
            //k=1:
            if(f[i-1][i-1][0]>a[i])
                f[i][0][1]=f[i-1][i-1][0]-a[i]+i*15;
            if(j>=1&&f[i-1][j-1][1]>a[i])
                f[i][j][1]=max(f[i][j][1],f[i-1][j-1][1]-a[i]);
            //k=2:
            if(j>=1&&f[i-1][j-1][2]>a[i])
                f[i][j][2]=max(f[i][j][2],f[i-1][j-1][2]-a[i]);
            if(j>=mid&&f[i-1][j-1][1]>a[i])
                f[i][0][2]=max(f[i][j][2],f[i-1][j-1][1]-a[i]+j*15);
                //这里的f[i][j][2]可以换成f[0][2],下同
            if(j>=mid&&f[i-1][j-1][2]>a[i])
                f[i][0][2]=max(f[i][j][2],f[i-1][j-1][2]-a[i]+j*15);
        }
    for(int i=0;i<=n;i++)
        if(f[n][i][0]>0||f[n][i][1]>0||f[n][i][2]>0) return 1;
        //判断,只要有一个>0就返回1
    return 0;
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&hp);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        int l=0,r=n+1;//r=n+1是为了方便判断没有上界的情况
        while(l<=r){//二分模板
            int mid=l+r>>1;
            if(check(mid)) l=mid+1;
            else r=mid-1;
        }
        if(r==n+1) puts("No upper bound.");
        else printf("%d\n",l-1);
    }
    return 0;
}
相关标签: 题解 OI