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

197 阅读5分钟

问题描述

小M最近在玩一款叫做“狼人拯救者”的游戏。在游戏中,玩家可以通过消耗金币来购买能力,这些能力会增加攻击力,但也会影响攻击速度。
小M是一个以攻击力为优先的玩家,但他必须保证自己的攻击速度不能低于0,因为攻击速度为负时将无法攻击。
现在小M面对商店中的N种能力,他拥有G枚金币和S点初始攻击速度。他想知道,在保持攻击速度大于等于0的前提下,他最多可以获得多少攻击力。

商店中每种能力用三元组表示为 array[i] = [c, s, d],其中:

  • c 表示购买该能力需要的金币数;
  • s 表示该能力对攻击速度的影响,可以为正数、零或负数;
  • d 表示该能力对攻击力的增加值。 题解:

在这道题中,我们需要帮助小M在一定的金币和初始攻击速度条件下,最大化其攻击力。游戏中有若干种可购买的能力,每个能力对攻击速度和攻击力有不同的影响。关键的约束条件是:攻击速度不能为负,否则无法攻击。

为了求解这个问题,我们可以采用动态规划(Dynamic Programming,简称DP)的思想。具体步骤如下:

问题分析

  • 金币:小M初始有 G 枚金币,购买能力需要消耗金币。
  • 攻击速度:每购买一种能力会增加或减少攻击速度。如果攻击速度为负,小M将无法继续攻击。
  • 攻击力:每购买一种能力会增加攻击力,目标是最大化攻击力。

我们需要在保证攻击速度不为负的前提下,最大化攻击力。因此,问题的核心就是在不违反攻击速度为负的约束下,尽可能多地购买能力来增加攻击力。

动态规划思路

  1. 定义状态:我们可以定义一个二维DP数组 dp[i][j],其中:

    • i 表示当前剩余的金币数。
    • j 表示当前的攻击速度。
    • dp[i][j] 表示在剩余 i 枚金币和攻击速度为 j 时,能够获得的最大攻击力。

    其中,dp[i][j] = -1 表示该状态不可达,意味着在该金币数和攻击速度的条件下,不可能进行有效的操作。

  2. 初始化

    • 初始状态下,dp[g][s] = 0,即拥有 G 枚金币和 S 点初始攻击速度时,攻击力为 0。
    • 其他状态初始化为 -1,表示不可达。
  3. 状态转移:对于每个能力,如果当前状态 dp[i][j] != -1(即当前金币和攻击速度是可行的),则考虑购买该能力:

    • 如果购买该能力后新的攻击速度 j + s 非负且不超过最大允许的速度,且剩余的金币数足够购买该能力,那么就更新状态。
    • 新的状态 dp[i - cost][j + speed] 会更新为 dp[i][j] + atk,表示在消耗金币后,攻击力得到了增加。
  4. 目标:最终我们需要在所有状态中找到最大的攻击力,即遍历 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;
}

解释

  1. 初始化 dp 数组:我们用二维数组 dp[i][j] 来表示状态,初始状态 dp[g][s] = 0 表示我们在起始状态下没有攻击力,而其他状态初始化为 -1,表示不可达。
  2. 处理每个能力:对于每种能力,我们依次检查是否能购买,并更新 dp 数组。我们使用一个临时数组 tempDp 来避免反复计算同一能力的影响,确保每种能力只能购买一次。
  3. 更新状态:对于每个能力,如果当前状态是可达的(dp[i][j] != -1),则计算购买该能力后的新状态,并更新 dp 数组。
  4. 寻找最大攻击力:在所有可达状态中,找出最大的攻击力,作为最终答案。

时间复杂度分析

  • 假设商店中有 N 种能力,金币数最大为 G,攻击速度的最大值为 maxSpeed。则动态规划的时间复杂度大约为 O(N * G * maxSpeed),因为我们需要遍历每种能力,并在 dp 数组中更新状态。

总结

本题的核心思想是通过动态规划求解在给定约束下的最优解。我们通过维护一个二维 dp 数组来记录不同状态下的最大攻击力,并在每步更新时保证攻击速度不为负。最终,我们通过遍历所有状态找到最大的攻击力,从而得到答案。