题解:学而思编程 超大容量背包
【题目来源】
学而思编程:超大容量背包
【题目描述】
有 \(n\) 个物品,编号从 \(1\) 到 \(n\),每个物品有一个重量 \(w_i\) 和价值 \(v_i\)。
Taro有一个背包,容量为 \(W\),他想要挑选一些物品装到背包里面,要求选择的物品重量和不能超过 \(W\)。
Taro想知道他能获得的最大价值是多少?
【输入】
第一行两个整数 \(n(1≤n≤100)\) 和 \(W(1≤W≤10^9)\)。
接下来 \(n\) 行,每行两个整数 \(w_i(1≤w_i≤W≤10^9)\) 和 \(v_i(1≤v_i≤10^3)\),表示第 \(i\) 个物品的重量和价值。
【输出】
输出一行,包含一个整数,表示Taro能获得的最大价值。
【输入样例】
3 8
3 30
4 50
5 60
【输出样例】
90
【核心思想】
-
问题分析:给定 \(n\) 个物品(重量 \(w_i\),价值 \(v_i\))和背包容量 \(W\)(\(W \leq 10^9\)),求在重量不超过 \(W\) 的情况下能获得的最大价值。由于 \(W\) 很大(\(10^9\)),无法使用常规的以重量为维度的DP。但价值 \(v_i \leq 10^3\) 且 \(n \leq 100\),总价值上限为 \(10^5\),可以使用以价值为维度的逆向DP。
-
算法选择:
- 逆向DP:
dp[j]表示获得价值 \(j\) 所需的最小重量 - 维度转换:将"重量限制下的最大价值"转化为"价值目标下的最小重量"
- 逆向DP:
-
关键步骤:
- 初始化:
dp[0] = 0(获得价值0需要重量0),其余为无穷大 - 状态转移:逆序遍历价值 \(j\)(从 \(v_i\) 到总价值上限):
dp[j] = min(dp[j], dp[j - v[i]] + w[i])(选或不选第 \(i\) 个物品)
- 统计答案:从高到低遍历价值 \(j\),找到第一个满足
dp[j] <= W的 \(j\) 即为最大价值
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times V_{total})\),其中 \(V_{total} = n \times \max(v_i) \leq 10^5\)
- 空间复杂度:\(O(V_{total})\),一维数组
-
01背包逆向DP的核心思想:
- 维度转换策略:当重量范围大、价值范围小时,将DP维度从重量转换为价值
- 逆向思维:
dp[j]表示获得价值 \(j\) 所需的最小重量,而非传统DP中"重量 \(j\) 下的最大价值" - 逆序遍历:01背包的一维优化需要逆序遍历,避免重复选择
- 结果查找:从高价值向低价值查找,第一个满足重量约束的值即为答案
- 适用于重量范围极大、价值范围较小的背包问题
【算法标签】
01背包
【代码详解】
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 105;
int n, m, w[N], v[N], dp[1000005]; // dp[j]: 获得价值j所需的最小重量
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
{
cin >> w[i] >> v[i]; // 重量, 价值
}
memset(dp, 0x3f, sizeof(dp)); // 初始化为无穷大
dp[0] = 0; // 价值0需要重量0
// 0/1背包:求获得价值j的最小重量
for (int i = 1; i <= n; i++)
{
for (int j = 1e5; j >= v[i]; j--)
{
dp[j] = min(dp[j], dp[j - v[i]] + w[i]);
}
}
// 从高到低查找不超过重量限制的最大价值
for (int j = 1e5; j >= 0; j--)
{
if (dp[j] <= m) // 重量不超过m
{
cout << j;
break;
}
}
return 0;
}
【运行结果】
3 8
3 30
4 50
5 60
90
浙公网安备 33010602011771号