信息学奥赛一本通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;
	}
}
posted @ 2026-10-06 16:37  DK_orz  阅读(5)  评论(0)    收藏  举报