青训营X豆包MarsCode 技术训练营第一课 | 豆包MarsCode AI 刷题

42 阅读5分钟

题目内容

小M最近沉迷一款名叫狼人拯救者的游戏。在这个游戏的游戏过程中,玩家可以花费一定的金币,来获取一些能力,这些能力会对玩家的攻击力和攻击速度造成影响。

对于所有的能力,在获得时总是会增加玩家的攻击力,但是有可能提高或是降低玩家的攻击速度。

作为攻击力至上的玩家,小M总是希望能够获得尽可能获得高攻击力。但是如果攻击速度小于0的话,就没有办法进行攻击了,所以必须保证最终的攻击速度大于等于0。

现在商店里有N个能力,小M有G枚金币与S点攻击速度。小M想知道他在最终的攻击速度大于等于0的前提下,最高可以获得多少点攻击力。

问题分析

在这道题目中,玩家需要在攻击速度大于等于 0 的前提下,通过合理分配金币,尽可能提升攻击力。问题的本质是一个0/1 背包问题的变形,其中金币是背包容量,攻击力是目标收益,而攻击速度需要满足额外约束。

具体来说:

  1. 约束条件

    • 玩家初始攻击速度 SS 不能小于 0。
    • 获得能力后,攻击速度 S+能力总攻击速度变化≥0S + \text{能力总攻击速度变化} \geq 0。
  2. 目标

    • 在满足约束的情况下,最大化攻击力。
  3. 输入

    • NN:能力数量。
    • GG:金币总量。
    • SS:初始攻击速度。
    • 每个能力的属性:金币消耗 cost[i]\text{cost}[i]、攻击力提升 attack[i]\text{attack}[i]、攻击速度变化 speed[i]\text{speed}[i]。

解题思路

我们可以用动态规划解决这个问题,思路如下:

  1. 定义状态

    • 使用 dp[g][s]dp[g][s] 表示在消耗 gg 枚金币并且攻击速度增加 ss 的前提下,能够达到的最高攻击力。
    • 攻击速度可以为负值,表示当前攻击速度变化,最终需要验证总和是否满足 S+speed≥0S + \text{speed} \geq 0。
  2. 状态转移方程

    • 若选择能力 ii,则: dp[g][s]=max⁡(dp[g][s],dp[g−cost[i]][s−speed[i]]+attack[i])dp[g][s] = \max(dp[g][s], dp[g - \text{cost}[i]][s - \text{speed}[i]] + \text{attack}[i])
    • 若不选择能力 ii,则保持现有状态 dp[g][s]dp[g][s]。
  3. 初始状态

    • dp[0][0]=0dp[0][0] = 0,即没有消耗金币时攻击力为 0。
    • 其余状态初始化为不可达。
  4. 约束条件处理

    • 最后遍历 dpdp 表时,仅保留攻击速度满足 S+s≥0S + s \geq 0 的状态。
  5. 优化

    • 使用二维动态规划,但可以用滚动数组将空间优化为 O(G×S)O(G \times S)。

实现代码

def max_attack(N, G, S, abilities):
    # abilities: [(cost, attack, speed)]
    dp = [[-float('inf')] * (S + 1) for _ in range(G + 1)]
    dp[0][0] = 0  # 初始状态

    for cost, attack, speed in abilities:
        for g in range(G, cost - 1, -1):
            for s in range(S, -speed - 1, -1):
                if dp[g - cost][s - speed] != -float('inf'):
                    dp[g][s] = max(dp[g][s], dp[g - cost][s - speed] + attack)

    # 取所有满足攻击速度 >= 0 的最大值
    max_attack_power = 0
    for g in range(G + 1):
        for s in range(S + 1):
            max_attack_power = max(max_attack_power, dp[g][s])

    return max_attack_power

# 示例输入
N = 3
G = 10
S = 5
abilities = [
    (4, 10, 1),  # 花费4金币,增加10攻击力,攻击速度+1
    (6, 20, -3), # 花费6金币,增加20攻击力,攻击速度-3
    (2, 5, 0),   # 花费2金币,增加5攻击力,攻击速度不变
]

print(max_attack(N, G, S, abilities))  # 输出:35

代码解析

1. 状态初始化

我们用一个二维数组 dp[g][s]dp[g][s] 存储每个金币数和攻击速度下的最大攻击力,将初始状态设置为:

  • 未使用任何能力时,攻击力为 0。
  • 其他状态初始化为不可达(设置为负无穷)。
2. 动态规划转移

对每个能力,从金币和攻击速度的上限向下遍历,检查是否可以选择该能力并更新最大攻击力。如果选择能力 ii,需要检查:

  • 当前金币是否足够。
  • 当前攻击速度是否在合理范围内。
3. 结果筛选

最后从所有满足 S+s≥0S + s \geq 0 的状态中,挑选最大攻击力的状态作为最终结果。


示例与边界测试

示例 1

输入

  • 能力数 N=3N = 3

  • 金币数 G=10G = 10

  • 初始攻击速度 S=5S = 5

  • 能力列表:

    • (4, 10, 1)
    • (6, 20, -3)
    • (2, 5, 0)

输出

  • 最优攻击力:35
    解释:选择能力 1 和能力 2,总金币消耗 4+6=104 + 6 = 10,总攻击力 10+20=3010 + 20 = 30,最终攻击速度 5+1−3=35 + 1 - 3 = 3。

边界测试
  1. 攻击速度永远不足

    • N=2N = 2, G=5G = 5, S=1S = 1
    • 能力列表:[(3, 10, -2), (4, 15, -3)]
    • 输出:0(没有能力可以满足攻击速度大于等于 0 的条件)。
  2. 金币不足

    • N=2N = 2, G=1G = 1, S=5S = 5
    • 能力列表:[(3, 10, 2), (4, 15, 3)]
    • 输出:0(没有足够金币购买任何能力)。
  3. 能力选择最优组合

    • N=4N = 4, G=10G = 10, S=5S = 5
    • 能力列表:[(2, 5, 0), (6, 20, -2), (4, 15, 1), (3, 10, -1)]
    • 输出:40(选择能力 2 和能力 3)。

时间复杂度与空间复杂度

  1. 时间复杂度

    • 外层能力循环 O(N)O(N)。
    • 内层金币和攻击速度循环 O(G×S)O(G \times S)。
    • 总复杂度:O(N×G×S)O(N \times G \times S)。
  2. 空间复杂度

    • O(G×S)O(G \times S) 的动态规划表。

总结

通过动态规划处理问题,可以高效解决攻击力与攻击速度的权衡问题。本解法不仅优化了计算效率,还清晰地处理了不满足条件的情况,适合在实际游戏开发和优化中应用。