题解:洛谷 P14920 道具商店
【题目来源】
洛谷:P14920 [GESP202512 六级] 道具商店 - 洛谷
【题目描述】
道具商店里有 \(n\) 件道具可供挑选。第 \(i\) 件道具可为玩家提升 \(a_i\) 点攻击力,需要 \(c_i\) 枚金币才能购买,每件道具只能购买一次。现在你有 \(k\) 枚金币,请问你最多可以提升多少点攻击力?
【输入】
第一行,两个正整数 \(n,k\),表示道具数量以及你所拥有的金币数量。
接下来 \(n\) 行,每行两个正整数 \(a_i,c_i\),表示道具所提升的攻击力点数,以及购买所需的金币数量。
【输出】
输出一行,一个整数,表示最多可以提升的攻击力点数。
【输入样例】
3 5
99 1
33 2
11 3
【输出样例】
132
【核心思想】
-
问题分析:给定 \(n\) 件道具,每件道具提供攻击力 \(a_i\),花费金币 \(c_i\),总预算 \(k\) 枚金币,每件只能购买一次。求预算内可获得的最大攻击力。这是一个01 背包问题,但采用逆向 DP 思路——将"攻击力"作为重量、"金币"作为成本,求获得指定攻击力所需的最小成本。
-
算法选择:
- 01 背包(逆向 DP):\(dp[j]\) 表示获得总攻击力 \(j\) 所需的最小金币数
- 答案提取:在所有可达的攻击力 \(j\) 中,找到成本 \(\leq k\) 的最大 \(j\)
-
关键步骤:
- 初始化:\(dp[0] = 0\)(获得 \(0\) 攻击力成本为 \(0\)),其余 \(dp[j] = \infty\)
- 读入数据:\(n\)(道具数)、\(k\)(金币数)、\((a_i, c_i)\)(攻击力和成本)
- 01 背包 DP(遍历 \(i\) 从 \(1\) 到 \(n\)):
- 更新总攻击力上限 \(sum = \sum_{p=1}^{i} a_p\)
- 倒序遍历 \(j\) 从 \(sum\) 到 \(a_i\):
- 状态转移:\(dp[j] = \min(dp[j], dp[j - a_i] + c_i)\)(不选或选第 \(i\) 件道具)
- 提取答案:遍历 \(j\) 从 \(0\) 到 \(sum\),若 \(dp[j] \leq k\) 则 \(ans = \max(ans, j)\)
- 输出答案 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times \sum a_i)\),其中 \(\sum a_i\) 为总攻击力上限
- 空间复杂度:\(O(\sum a_i)\),一维 \(dp\) 数组
-
01 背包的核心思想:
- 逆向思维转换:当传统背包(容量 \(\to\) 最大价值)的容量 \(k\) 过大时,可将"价值"作为重量、"重量"作为成本,求"获得指定价值的最小重量"
- 倒序遍历保证每件只选一次:\(j\) 从大到小遍历,确保 \(dp[j - a_i]\) 是未选第 \(i\) 件时的状态
- 状态压缩:一维数组滚动更新,\(dp[j]\) 同时承担"不选"(原值)和"选"(\(dp[j-a_i]+c_i\))两种状态的比较
- 答案域枚举:DP 完成后遍历所有可能的攻击力值,找到预算约束下的最大值
- 适用于容量过大但价值范围有限的背包问题、资源分配、成本优化类问题
【解题思路】

【算法标签】
普及 #01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 505;
int n, k; // n: 物品数量, k: 预算上限
int a[N]; // 存储每个物品的重量
int c[N]; // 存储每个物品的成本
int dp[N * N]; // 动态规划数组,dp[j]表示获得总重量j的最小成本
int sum; // 所有物品的总重量
int ans; // 最终答案,在预算内能获得的最大总重量
signed main()
{
// 初始化动态规划数组,所有值设为无穷大(表示不可达状态)
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0; // 获得总重量0的成本为0
cin >> n >> k; // 输入物品数量和预算上限
// 输入每个物品的重量和成本
for (int i = 1; i <= n; i++)
{
cin >> a[i] >> c[i];
}
// 动态规划:01背包问题
for (int i = 1; i <= n; i++)
{
// 更新当前物品能获得的总重量范围
sum += a[i];
// 从后向前遍历,确保每个物品只使用一次
for (int j = sum; j >= a[i]; j--)
{
// 状态转移:选择当前物品或不选择当前物品
// dp[j] = min(不选当前物品, 选当前物品)
dp[j] = min(dp[j], dp[j - a[i]] + c[i]);
}
}
// 在所有可能的重量中,找到成本不超过预算的最大重量
for (int j = 0; j <= sum; j++)
{
if (dp[j] <= k) // 如果获得重量j的成本不超过预算
{
ans = max(ans, j); // 更新最大重量
}
}
cout << ans << endl; // 输出结果
return 0; // 程序正常结束
}
【运行结果】
3 5
99 1
33 2
11 3
132
浙公网安备 33010602011771号