问题描述
小M最近在玩一款叫做“狼人拯救者”的游戏。在游戏中,玩家可以通过消耗金币来购买能力,这些能力会增加攻击力,但也会影响攻击速度。
小M是一个以攻击力为优先的玩家,但他必须保证自己的攻击速度不能低于0,因为攻击速度为负时将无法攻击。
现在小M面对商店中的N种能力,他拥有G枚金币和S点初始攻击速度。他想知道,在保持攻击速度大于等于0的前提下,他最多可以获得多少攻击力。
商店中每种能力用三元组表示为 array[i] = [c, s, d],其中:
c表示购买该能力需要的金币数;s表示该能力对攻击速度的影响,可以为正数、零或负数;d表示该能力对攻击力的增加值。
问题分析
在游戏中,玩家可以从商店中购买N种不同的能力。每个能力都有三个参数:
- c:购买该能力需要的金币数。
- s:该能力对攻击速度的影响,可以是正数、零或负数。
- d:该能力增加的攻击力。
玩家的目标是:在保持攻击速度不为负的情况下,最大化攻击力。
限制条件:
- 玩家有G枚金币。
- 玩家有S点初始攻击速度。
- 每个能力消耗金币,可能影响攻击速度,但每次购买都不能使攻击速度低于0。
动态规划思想
这个问题是典型的01背包问题的变体,我们将其转化为背包问题:
- 物品:每项能力
[c, s, d]对应一个物品。- 物品的重量是
c(金币数)。 - 物品的价值是
d(攻击力增加)。 - 物品对背包的容量(攻击速度)有影响,
s就是对攻击速度的变化。
- 物品的重量是
问题的关键是,我们需要根据能力的选择,更新一个动态规划数组来保存每个可能状态下的最大攻击力。
dp数组的定义
我们定义一个二维的dp数组:
dp[i][j]:表示拥有i金币,攻击速度为j时,所能获得的最大攻击力。
初始化
- 初始时,
dp[0][j](即金币为0时,所有攻击速度)为0,因为如果没有购买任何能力,攻击力当然为0。 - 其他状态的初始化只要不影响状态转移就行,因为这里的攻击力增加值一定为正,所以我们可以一起初始化为0。
状态转移公式
对于每个能力 [c, s, d],如果当前金币数 i 大于等于 c,并且攻击速度 j + s 不小于0(即攻击速度合法),那么我们可以选择是否购买该能力。购买后,新的状态转移为:
dp[i][j] = max(dp[i][j], dp[i - c][max(0, j + s)] + d)
dp[i][j]表示购买该能力后,状态的最大攻击力。max(0, j + s)是为了确保攻击速度不低于0。
目标
我们要找到在所有金币数不超过 G 且攻击速度不超过 S 的情况下,最大攻击力。
完整代码实现
def solution(n, g, s, abilities):
# dp[i][j] 表示使用 i 金币时,攻击速度为 j 时的最大攻击力
dp = [[0] * (s + 1) for _ in range(g + 1)]
# 遍历每个能力
for c, delta_s, delta_d in abilities:
for i in range(g, c - 1, -1): # 从大到小遍历金币数
for j in range(s, -1, -1): # 从大到小遍历攻击速度
if j + delta_s >= 0: # 如果攻击速度合法(>=0)
dp[i][j] = max(dp[i][j], dp[i - c][min(j + delta_s, s)] + delta_d)
# 查找所有状态中的最大攻击力
max_attack = 0
for i in range(g + 1):
for j in range(s + 1):
if dp[i][j] != -1: # 只有合法的状态才更新最大值
max_attack = max(max_attack, dp[i][j])
return max_attack
# 测试用例
if __name__ == "__main__":
test1 = [
[71, -51, 150],
[40, 50, 100],
[40, 50, 100],
[30, 30, 70],
[30, 30, 70],
[30, 30, 70],
]
print(solution(6, 100, 100, test1) == 240) # 预期结果:240
test2 = [
[71, -51, 150],
[40, -50, 100],
[40, -50, 100],
[30, -30, 70],
[30, -30, 70],
[30, -30, 70],
]
print(solution(6, 100, 100, test2) == 210) # 预期结果:210
代码解析
dp数组初始化:初始化时,dp[0][j]设置为0,表示如果没有购买任何能力时,攻击力为0。- 遍历能力:我们逐个遍历商店中的能力,每个能力会影响金币数和攻击速度,更新动态规划数组
dp。 - 状态转移:对于每项能力
[c, delta_s, delta_d],我们尝试购买该能力并更新dp[i][j],确保攻击速度不为负且金币数不超过G。 - 最大攻击力:在所有可能的状态中,找出最大攻击力。
时间复杂度分析
- 状态转移的三重循环:外层循环遍历能力、第二层循环遍历金币数、第三层循环遍历攻击速度。
- 最坏情况下,时间复杂度为 O(n * G * S),其中
n是能力数量,G是金币数,S是攻击速度。