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

61 阅读4分钟

题目详情

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

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

  • c 表示购买该能力需要的金币数;
  • s 表示该能力对攻击速度的影响,可以为正数、零或负数;
  • d 表示该能力对攻击力的增加值。
  • 在本题当中,在技能列表中遍历,我们每轮的决策在于选择或不选择该技能
  • 有n项技能。
  • 拥有金币G,每个技能价格为c,条件为G>=0。
  • 有攻击速度S,每个技能会加上s(s>=0||s<0),条件为S>=0。
  • 有攻击力D=0,每个技能会加上d(d>=0)。
  • 求最高攻击力
1. 思考每轮的决策,定义状态,从而得到dp表:

对于每项技能来说,不消耗金币购买,剩余金币不变;消耗金币购买,剩余金币减少。由此可得状态定义:当前技能编号i,和剩余金币g,记为[i,g]。 状态[i,g]对应的子问题为:前i项技能在可用金币为g时所累积的最高攻击力,记为dp[i,g]。 待求解的是dp[n,G],因此需要一个尺寸为(n+1)*(G+1)的二位dp表。

2. 找出最优子结构,进而推导出状态转移方程:
  • 不购买该技能:金币不变,状态变为[i-1,g]
  • 购买该技能:金币减少为g-array[i][0],攻击力增加为D+array[i][2],攻击速度变为s+array[i][1]
  • 最优子结构为:最大攻击力dp[i,g]等于购买技能i和不购买技能i两种方案中,攻击力最大的那一个。再加上条件攻击速度>0。
有状态转移方程为:
`if(s+array[i][1]>=0&&g>=array[i][0]){
    dp[i,g]=max(dp[i-1,g]),dp[i-1,g-array[i][0]]+array[i-1][2]
}else{
    dp[i,g]=dp[i-1,g]
}`
3. 确定边界条件和状态转移顺序:

当无剩余金币或者无技能列表的时候,最大攻击力为0。即首列dp[i,0],首行dp[0,g]都为0。

代码实现:
public class Main {
    public static int solution(int n, int g, int s, int[][] array) {
        // 定义一个二维数组 dp[i][j] 表示在前 i 种能力中,花费 j 枚金币时,能够获得的最大攻击力
        int[][] dp = new int[n + 1][g + 1];

        // 初始化 dp 数组,初始攻击速度为 s
        int[][] speed = new int[n + 1][g + 1];
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= g; j++) {
                speed[i][j] = s;
            }
        }

        // 遍历每种能力
        for (int i = 1; i <= n; i++) {
            int cost = array[i - 1][0];
            int speedChange = array[i - 1][1];
            int damage = array[i - 1][2];

            // 遍历每种可能的金币花费
            for (int j = 0; j <= g; j++) {
                // 不购买当前能力
                dp[i][j] = dp[i - 1][j];
                speed[i][j] = speed[i - 1][j];

                // 如果可以购买当前能力,并且购买后攻击速度不低于0
                if (j >= cost && speed[i - 1][j - cost] + speedChange >= 0) {
                    // 计算购买后的攻击力和攻击速度
                    int newDamage = dp[i - 1][j - cost] + damage;
                    int newSpeed = speed[i - 1][j - cost] + speedChange;

                    // 更新 dp 和 speed 数组
                    if (newDamage > dp[i][j]) {
                        dp[i][j] = newDamage;
                        speed[i][j] = newSpeed;
                    }
                }
            }
        }

        // 找到在所有可能的金币花费下,攻击力的最大值
        int maxDamage = 0;
        for (int j = 0; j <= g; j++) {
            if (speed[n][j] >= 0) {
                maxDamage = Math.max(maxDamage, dp[n][j]);
            }
        }

        return maxDamage;
    }

    public static void main(String[] args) {
        // Add your test cases here
        int[][] test1 = {
            {71, -51, 150},
            {40, 50, 100},
            {40, 50, 100},
            {30, 30, 70},
            {30, 30, 70},
            {30, 30, 70}
        };
        System.out.println(solution(6, 100, 100, test1) == 240);

        int[][] test2 = {
            {71, -51, 150},
            {40, -50, 100},
            {40, -50, 100},
            {30, -30, 70},
            {30, -30, 70},
            {30, -30, 70}
        };
        System.out.println(solution(6, 100, 100, test2) == 210);
    }
}

头一次接触动态规划的题,之前一次也没有做过,今天头一次认真做了,也算是小小的进步了~