题解:学而思编程 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
【核心思想】
-
问题分析:给定怪物体力 \(H\) 和 \(N\) 种魔法,每种魔法可减少 \(a_i\) 点体力并消耗 \(b_i\) 点魔法值,魔法使用次数无限制。目标是使怪物体力 \(\leq 0\) 的最小魔法值消耗。这是一个完全背包问题:将魔法视为物品,\(a_i\) 为"体积"(减少的体力),\(b_i\) 为"代价"(消耗的魔法值),求达到至少 \(H\) "体积"的最小代价。
-
算法选择:
- 完全背包动态规划:每种物品(魔法)可以无限次选取
- 二维DP状态:\(f[i][j]\) 表示考虑前 \(i\) 种魔法,总减少体力恰好为 \(j\) 时的最小魔法值消耗
- 状态压缩优化:可以使用一维数组优化空间
-
关键步骤:
-
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\)
-
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times M)\),其中 \(M\) 为DP数组的第二维上限(约 \(15000\))
- 空间复杂度:\(O(N \times M)\),二维DP数组;可优化至 \(O(M)\) 使用一维数组
-
完全背包的核心思想:
- 问题转化:将"减少体力"视为背包容量,"消耗魔法值"为物品代价,求达到目标容量的最小代价
- 状态定义:\(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
浙公网安备 33010602011771号