题解:学而思编程 vs怪物5

【题目来源】

学而思编程:vs怪物5

【题目描述】

Toki在和一只怪物战斗,怪物的体力是 \(H\)

Toki可以使用 \(N\) 种魔法,使用一次第 \(i\) 种魔法可以减少怪物 \(a_i\) ​点体力,需要消耗 \(b_i\) ​点魔法值。魔法的使用次数没有任何限制。

通过使用魔法,让怪物体力减少到小于等于 \(0\) 点,Toki最少需要消耗多少魔法值?

【输入】

\(1\) 行,\(2\) 个正整数 \(H,N\)

接下来 \(N\) 行,每行两个正整数 \(a_i,b_i\)

【输出】

输出一个整数,需要的最少魔法值。

【输入样例】

9 3
8 3
4 2
2 1

【输出样例】

4

【核心思想】

  1. 问题分析:给定怪物体力 \(H\)\(N\) 种魔法,每种魔法可减少 \(a_i\) 点体力并消耗 \(b_i\) 点魔法值,魔法使用次数无限制。目标是使怪物体力 \(\leq 0\) 的最小魔法值消耗。这是一个完全背包问题:将魔法视为物品,\(a_i\) 为"体积"(减少的体力),\(b_i\) 为"代价"(消耗的魔法值),求达到至少 \(H\) "体积"的最小代价。

  2. 算法选择

    • 完全背包动态规划:每种物品(魔法)可以无限次选取
    • 二维DP状态\(f[i][j]\) 表示考虑前 \(i\) 种魔法,总减少体力恰好为 \(j\) 时的最小魔法值消耗
    • 状态压缩优化:可以使用一维数组优化空间
  3. 关键步骤

    • Step 1 - 初始化

      • 读取 \(H\)(目标体力)和 \(n\)(魔法种类数)
      • 读取每种魔法的 \(a_i\)(减少体力)和 \(b_i\)(消耗魔法值)
      • 初始化 DP 数组为无穷大,memset(f, 0x3f, sizeof(f))
      • 边界条件:\(f[0][0] = 0\)(前0种魔法,减少0体力,消耗0魔法值)
    • Step 2 - 完全背包DP

      • 遍历每种魔法 \(i\)\(1\)\(n\)
      • 遍历总减少体力 \(j\)\(0\)\(M\)\(M\) 为足够大的上限,如 \(15000\)
      • 不选第 \(i\) 种魔法\(f[i][j] = f[i-1][j]\)
      • 选第 \(i\) 种魔法(完全背包,可重复):若 \(j \geq a_i\),则 \(f[i][j] = \min(f[i][j], f[i][j-a_i] + b_i])\)
      • 注意:完全背包使用 \(f[i][j-a_i]\) 而非 \(f[i-1][j-a_i]\),表示可以重复选取
    • Step 3 - 统计答案

      • 答案为所有 \(j \geq H\)\(f[n][j]\) 的最小值
      • ans = min(ans, f[n][j]) 对于 \(j \in [H, M)\)
    • Step 4 - 输出结果

      • 输出最小魔法值消耗 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times M)\),其中 \(M\) 为DP数组的第二维上限(约 \(15000\)
    • 空间复杂度:\(O(N \times M)\),二维DP数组;可优化至 \(O(M)\) 使用一维数组
  5. 完全背包的核心思想

    • 问题转化:将"减少体力"视为背包容量,"消耗魔法值"为物品代价,求达到目标容量的最小代价
    • 状态定义\(f[i][j]\) 表示前 \(i\) 种物品,恰好装满容量 \(j\) 的最小代价
    • 状态转移:完全背包与01背包的区别在于使用 \(f[i][j-a_i]\) 而非 \(f[i-1][j-a_i]\),允许同一物品重复选取
    • 答案处理:由于要求怪物体力 \(\leq 0\),需要在所有 \(j \geq H\) 的状态中取最小值,而非恰好 \(j = H\)
    • 空间优化:可以使用滚动数组将空间复杂度降至 \(O(M)\)
    • 适用于资源分配、成本优化、无限选取类问题

【算法标签】

完全背包

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// ================= 常量与全局变量 =================

const int N = 1005;          // 最大魔法种类数
const int M = 15005;         // 最大体力上限(足够覆盖 H 的范围)
const int INF = 1e9;         // 无穷大常量

int H;                       // 怪物的初始体力
int n;                       // 魔法种类数量

int a[N];                    // 每种魔法减少的体力值
int b[N];                    // 每种魔法消耗的魔法值

int f[N][M];                 // 动态规划数组
                             // f[i][j] 表示使用前 i 种魔法,造成恰好 j 点伤害时的最小魔法值消耗

// ================= 主函数 =================
int main()
{
    // 读取怪物体力和魔法种类数
    cin >> H >> n;

    // 读取每种魔法的伤害和消耗
    for (int i = 1; i <= n; i++)
        cin >> a[i] >> b[i];

    // 初始化 dp 数组为无穷大
    memset(f, 0x3f, sizeof(f));

    // 边界条件:使用 0 种魔法,造成 0 点伤害时,消耗为 0
    f[0][0] = 0;

    // 完全背包 DP
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j < M; j++)
        {
            // 不使用第 i 种魔法
            f[i][j] = f[i - 1][j];

            // 使用第 i 种魔法(完全背包,可重复使用)
            if (j >= a[i])
                f[i][j] = min(f[i][j], f[i][j - a[i]] + b[i]);
        }
    }

    // 统计答案:在所有造成伤害 ≥ H 的状态中取最小消耗
    int ans = INF;
    for (int j = H; j < M; j++)
        ans = min(ans, f[n][j]);

    // 输出最少魔法值消耗
    cout << ans << endl;

    return 0;
}

【运行结果】

9 3
8 3
4 2
2 1
4
posted @ 2026-06-17 09:28  团爸讲算法  阅读(4)  评论(0)    收藏  举报