小M的能力选择挑战 | 豆包MarsCode AI刷题

70 阅读5分钟

问题描述

小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

代码解析

  1. dp 数组初始化:初始化时,dp[0][j] 设置为0,表示如果没有购买任何能力时,攻击力为0。
  2. 遍历能力:我们逐个遍历商店中的能力,每个能力会影响金币数和攻击速度,更新动态规划数组 dp
  3. 状态转移:对于每项能力 [c, delta_s, delta_d],我们尝试购买该能力并更新 dp[i][j],确保攻击速度不为负且金币数不超过G。
  4. 最大攻击力:在所有可能的状态中,找出最大攻击力。

时间复杂度分析

  • 状态转移的三重循环:外层循环遍历能力、第二层循环遍历金币数、第三层循环遍历攻击速度。
  • 最坏情况下,时间复杂度为 O(n * G * S),其中 n 是能力数量,G 是金币数,S 是攻击速度。