题目链接
题目描述
小美按顺序遇到 只怪物(编号为
),第
只怪物的生命值为
。对于每只怪物,小美有两个选择:
- 放走:获得
点经验值。
- 击败:获得
点基础经验值,并额外获得
点奖励经验值,其中
是到目前为止累计击败的怪物总数(包括当前这只)。
请求出小美在处理完所有 只怪物后,能获得的最大总经验值。
解题思路
这是一个动态规划问题。核心的观察点在于,击败怪物的额外收益仅仅取决于已击败怪物数量对10取模的余数,而不是击败怪物的确切总数。这个特性使得我们可以大大优化DP的状态表示。
1. DP状态定义
由于奖励只和击败数量的个位数有关,我们不需要记录完整的击败数量。
我们定义一个一维DP数组,大小为10:
:表示在考虑完若干怪物后,已击败的怪物总数模10的余数为
时,所能获得的最大经验值。
2. DP状态转移
我们按顺序遍历每只怪物。当我们考虑第 只怪物(生命值为
)时,我们需要根据前一阶段的DP状态来计算当前阶段的新状态。为了避免在一次迭代中混淆新旧状态,我们使用一个临时数组
new_dp
来存储当前阶段的结果。
假设我们已经处理完前 只怪物,
dp
数组存储着对应的最大经验值。现在我们来处理第 只怪物:
遍历前一阶段的所有可能状态 j
from 0
to 9
(即击败了 只怪物,且
):
-
如果
dp[j]
是一个可达状态(即经验值有效),我们基于这个状态做决策:-
决策一:放走第
只怪物。
- 击败数不变,所以模10的余数仍然是
。
- 获得的经验是在原有基础上增加
。
- 我们可以用
dp[j] + i
这个值去更新new_dp[j]
。
- 击败数不变,所以模10的余数仍然是
-
决策二:击败第
只怪物。
- 击败数从
变为
。新的模10余数是
(j + 1) % 10
。 - 这是第
只被击败的怪物,所以奖励乘数是
。
- 获得的经验是在
dp[j]
基础上增加。
- 我们用这个值去更新
new_dp[(j + 1) % 10]
。
- 击败数从
-
-
迭代完所有的
j
后,new_dp
就包含了处理完第只怪物后的所有状态。我们将其复制回
dp
数组,进行下一轮迭代。
3. 初始化和最终答案
- 初始化:在遇到第一只怪物之前,我们击败了0只怪物,经验为0。所以
dp[0] = 0
,而其他状态dp[1...9]
都是不可达的,可以初始化为一个极小值(或-1,因为经验值非负)。 - 最终答案:在处理完所有
只怪物后,
dp
数组中的最大值就是我们所求的最大总经验。
代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n;
cin >> n;
vector<long long> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
// dp[j] 表示击败怪物数 mod 10 = j 时的最大经验
vector<long long> dp(10, -1);
dp[0] = 0;
for (int i = 0; i < n; ++i) {
vector<long long> new_dp = dp;
for (int j = 0; j < 10; ++j) {
if (dp[j] == -1) continue; // 跳过不可达状态
// 决策1: 放走第 i+1 只怪物
long long pass_exp = dp[j] + (i + 1);
new_dp[j] = max(new_dp[j], pass_exp);
// 决策2: 击败第 i+1 只怪物
int next_mod = (j + 1) % 10;
long long defeat_exp = dp[j] + a[i] * (1 + next_mod);
new_dp[next_mod] = max(new_dp[next_mod], defeat_exp);
}
dp = new_dp;
}
long long max_exp = 0;
for (int j = 0; j < 10; ++j) {
max_exp = max(max_exp, dp[j]);
}
cout << max_exp << endl;
return 0;
}
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long[] a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
}
// dp[j] 表示击败怪物数 mod 10 = j 时的最大经验
long[] dp = new long[10];
Arrays.fill(dp, -1);
dp[0] = 0;
for (int i = 0; i < n; i++) {
long[] newDp = dp.clone();
for (int j = 0; j < 10; j++) {
if (dp[j] == -1) continue; // 跳过不可达状态
// 决策1: 放走第 i+1 只怪物
long passExp = dp[j] + (i + 1);
newDp[j] = Math.max(newDp[j], passExp);
// 决策2: 击败第 i+1 只怪物
int nextMod = (j + 1) % 10;
long defeatExp = dp[j] + a[i] * (1 + nextMod);
newDp[nextMod] = Math.max(newDp[nextMod], defeatExp);
}
dp = newDp;
}
long maxExp = 0;
for (long exp : dp) {
maxExp = Math.max(maxExp, exp);
}
System.out.println(maxExp);
}
}
n = int(input())
a = list(map(int, input().split()))
# dp[j] 表示击败怪物数 mod 10 = j 时的最大经验
dp = [-1] * 10
dp[0] = 0
for i in range(n):
new_dp = dp[:]
for j in range(10):
if dp[j] == -1:
continue
# 决策1: 放走第 i+1 只怪物
pass_exp = dp[j] + (i + 1)
new_dp[j] = max(new_dp[j], pass_exp)
# 决策2: 击败第 i+1 只怪物
next_mod = (j + 1) % 10
defeat_exp = dp[j] + a[i] * (1 + next_mod)
new_dp[next_mod] = max(new_dp[next_mod], defeat_exp)
dp = new_dp
print(max(dp))
算法及复杂度
- 算法:动态规划
- 时间复杂度:
。我们遍历
个怪物,每次遍历更新大小为10的DP数组,总操作次数与
成正比。
- 空间复杂度:
。DP数组的大小是常数10。