信息学奥赛一本通1727:魔棒
原题链接:https://ybt.ssoier.cn/ide1.php?pid=1727
【题目描述】
有一个英雄,初始生命值是hp(生命值无上限),在接下来的n秒内,每秒会受到一次伤害,第i秒受到的伤害值为a[i]。这个英雄有一个道具“魔杖”,魔杖的初始能量为0,每受到一次伤害,积攒一点能量。在英雄受到伤害后,可以立即释放魔棒中的能量,恢复15×[能量点数]的生命值,且魔棒的点数清零。释放能量有施法间隔cd(cd是正整数),即相邻的两次释放的时间间隔至少有cd秒。任何时刻当hp≤0时视为死亡,问这个英雄存活下来的前提下,cd的值最大可以是多少?
注意,若a[i]为负,受到“伤害”后实际上生命值是增加的,魔棒仍然积攒能量。
【输入】
第一行两个正整数n,hp,含义如题目所述。
第二行n个整数,分别是a[1]..a[n]。
【输出】
一个数,最大的cd,cd是一个正整数。
如果cd没有上限,输出“No upper bound.”;如果无论如何都不能存活,输出−1。
【输入样例】
7 30
20 5 30 4 10 5 20
【输出样例】
2
【提示】
【数据规模】
对于100%的数据,n≤500,|a[i]|≤1000
二分+dp(贪心优化)
复杂度\(O(n^2logn)\)
这里有个细节需要注意,你使用能量必须要先扣血,如果你有能量的情况下发现已经死了是用不了能量的。
显然,cd具有单调性,可以用二分,一般来说,不是二分+贪心就是二分+dp,经过尝试发现贪心不行,那就是二分+dp啦
问题是dp的状态怎么设计,我们发现,要完整的描述一个状态需要三个量,现在考虑到哪了,有几个能量,上一次使用隔了多久,太麻烦了,根据我们之前贪心的思考,可以发现对于每一次使用,直接全部用完就可以了,这个不再证明。
于是就有了这个dp设计,dp[i][j]表示考虑完i后剩j个能量的最大血量。
因为我们每次直接用完了所有的能量,那么j就还表示上次用隔了多久,转移起来就很方便了。
1.这次不用,那么就直接是\(dp[i][j] = dp[i - 1][j - 1] - a[i] <= 0 ? -INF : dp[i - 1][j - 1] - a[i]\)把非法的标记成-INF
2.这次用,那么肯定剩下0个能量,就直接有\(dp[i][0] = max_{cd-1}^{i-1}(dp[i][0], dp[i - 1][j] - a[i] + 15 * (j + 1))\),当然要记得把\(dp[i-1][j]-a[i]<0\)的情况剪掉
最后再处理一下开始的情况,最开始是不考虑cd可以直接用的,我们如果要用的话其实加的血和扣的血是固定的,直接一个前缀和就行了。
code:
#include<iostream>
#include<algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 505;
int read() {
int k = 0, f = 1;char c = getchar();
for (;c < '0' || c>'9';c = getchar())if (c == '-')f = -1;
for (;c >= '0' && c <= '9';c = getchar())k = (k << 1) + (k << 3) + (c ^ 48);
return k * f;
}
int n, hp;
int a[N],S[N];
int dp[N][N];
bool check(int cd) {
memset(dp, -INF, sizeof(dp));
dp[0][0] = hp;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= i;j++) {
dp[i][j] = dp[i - 1][j - 1] - a[i] <= 0 ? -INF : dp[i - 1][j - 1] - a[i];
}
if (hp - S[i] > 0)dp[i][0] = hp - S[i] + 15 * i;
for (int j = cd - 1;j < i;j++) {
if (dp[i - 1][j] - a[i] <= 0)continue;
dp[i][0] = max(dp[i][0], dp[i - 1][j] - a[i] + 15 * (j + 1));
}
}
for (int j = 0;j <= n;j++)if (dp[n][j] > 0)return true;
return false;
}
int main() {
n = read(), hp = read();
for (int i = 1;i <= n;i++)a[i] = read(), S[i] = S[i - 1] + a[i];
if (!check(1))cout << -1;
else if (check(n+1))cout << "No upper bound.";
else {
int l = 1, r = n, ans = 1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid))l = mid + 1, ans = mid;
else r = mid - 1;
}
cout << ans;
}
}

浙公网安备 33010602011771号