问题描述
小M最近在玩一款叫做“狼人拯救者”的游戏。在游戏中,玩家可以通过消耗金币来购买能力,这些能力会增加攻击力,但也会影响攻击速度。
小M是一个以攻击力为优先的玩家,但他必须保证自己的攻击速度不能低于0,因为攻击速度为负时将无法攻击。
现在小M面对商店中的N种能力,他拥有G枚金币和S点初始攻击速度。他想知道,在保持攻击速度大于等于0的前提下,他最多可以获得多少攻击力。
商店中每种能力用三元组表示为 array[i] = [c, s, d],其中:
c表示购买该能力需要的金币数;s表示该能力对攻击速度的影响,可以为正数、零或负数;d表示该能力对攻击力的增加值。 题解:
在这道题中,我们需要帮助小M在一定的金币和初始攻击速度条件下,最大化其攻击力。游戏中有若干种可购买的能力,每个能力对攻击速度和攻击力有不同的影响。关键的约束条件是:攻击速度不能为负,否则无法攻击。
为了求解这个问题,我们可以采用动态规划(Dynamic Programming,简称DP)的思想。具体步骤如下:
问题分析
- 金币:小M初始有 G 枚金币,购买能力需要消耗金币。
- 攻击速度:每购买一种能力会增加或减少攻击速度。如果攻击速度为负,小M将无法继续攻击。
- 攻击力:每购买一种能力会增加攻击力,目标是最大化攻击力。
我们需要在保证攻击速度不为负的前提下,最大化攻击力。因此,问题的核心就是在不违反攻击速度为负的约束下,尽可能多地购买能力来增加攻击力。
动态规划思路
-
定义状态:我们可以定义一个二维DP数组
dp[i][j],其中:i表示当前剩余的金币数。j表示当前的攻击速度。dp[i][j]表示在剩余i枚金币和攻击速度为j时,能够获得的最大攻击力。
其中,
dp[i][j] = -1表示该状态不可达,意味着在该金币数和攻击速度的条件下,不可能进行有效的操作。 -
初始化:
- 初始状态下,
dp[g][s] = 0,即拥有 G 枚金币和 S 点初始攻击速度时,攻击力为 0。 - 其他状态初始化为
-1,表示不可达。
- 初始状态下,
-
状态转移:对于每个能力,如果当前状态
dp[i][j] != -1(即当前金币和攻击速度是可行的),则考虑购买该能力:- 如果购买该能力后新的攻击速度
j + s非负且不超过最大允许的速度,且剩余的金币数足够购买该能力,那么就更新状态。 - 新的状态
dp[i - cost][j + speed]会更新为dp[i][j] + atk,表示在消耗金币后,攻击力得到了增加。
- 如果购买该能力后新的攻击速度
-
目标:最终我们需要在所有状态中找到最大的攻击力,即遍历
dp数组,找出所有可达状态中的最大值。
动态规划的具体实现
java
复制代码
public static int solution(int n, int g, int s, int[][] array) {
// 初始化 dp 数组,表示所有状态初始为 -1,表示不可达
int maxSpeed = 100 * s + 1;
int[][] dp = new int[g + 1][maxSpeed];
// 初始化 dp 数组为 -1
for (int i = 0; i <= g; i++) {
for (int j = 0; j < maxSpeed; j++) {
dp[i][j] = -1; // 标记所有状态为不可达
}
}
dp[g][s] = 0; // 起始状态:拥有 g 金币,速度为 s,攻击力为 0
// 处理每个能力并更新 dp 表
for (int[] ability : array) {
int cost = ability[0]; // 能力的金币消耗
int speed = ability[1]; // 能力增加的速度
int atk = ability[2]; // 能力增加的攻击力
// 使用临时数组来保存更新后的 dp 值,避免能力反复购买
int[][] tempDp = new int[g + 1][maxSpeed];
for (int i = 0; i <= g; i++) {
for (int j = 0; j < maxSpeed; j++) {
tempDp[i][j] = dp[i][j]; // 将当前 dp 数组的状态复制到临时数组
}
}
// 更新 dp 数组,确保能力只购买一次
for (int i = g; i >= cost; i--) {
for (int j = maxSpeed - 1; j >= 0; j--) {
if (dp[i][j] != -1) { // 只有当前状态可达才进行更新
int newSpeed = j + speed;
if (newSpeed >= 0 && newSpeed < maxSpeed) {
tempDp[i - cost][newSpeed] = Math.max(tempDp[i - cost][newSpeed], dp[i][j] + atk);
}
}
}
}
// 将临时数组的结果写回 dp 数组
for (int i = 0; i <= g; i++) {
for (int j = 0; j < maxSpeed; j++) {
dp[i][j] = tempDp[i][j]; // 更新原始 dp 数组
}
}
}
// 从 dp 表中找到最大攻击力
int maxAtk = 0;
for (int i = 0; i <= g; i++) {
for (int j = 0; j < maxSpeed; j++) {
if (dp[i][j] != -1) {
maxAtk = Math.max(maxAtk, dp[i][j]);
}
}
}
return maxAtk;
}
解释
- 初始化
dp数组:我们用二维数组dp[i][j]来表示状态,初始状态dp[g][s] = 0表示我们在起始状态下没有攻击力,而其他状态初始化为-1,表示不可达。 - 处理每个能力:对于每种能力,我们依次检查是否能购买,并更新
dp数组。我们使用一个临时数组tempDp来避免反复计算同一能力的影响,确保每种能力只能购买一次。 - 更新状态:对于每个能力,如果当前状态是可达的(
dp[i][j] != -1),则计算购买该能力后的新状态,并更新dp数组。 - 寻找最大攻击力:在所有可达状态中,找出最大的攻击力,作为最终答案。
时间复杂度分析
- 假设商店中有 N 种能力,金币数最大为 G,攻击速度的最大值为
maxSpeed。则动态规划的时间复杂度大约为 O(N * G * maxSpeed),因为我们需要遍历每种能力,并在dp数组中更新状态。
总结
本题的核心思想是通过动态规划求解在给定约束下的最优解。我们通过维护一个二维 dp 数组来记录不同状态下的最大攻击力,并在每步更新时保证攻击速度不为负。最终,我们通过遍历所有状态找到最大的攻击力,从而得到答案。