学完本教程,你将能够解决编程竞赛中常见的物品数量有限制的背包问题。
第一步:理解双重背包问题
双重背包是指每个物品不仅有一个价值和一个重量,还有一个数量限制,即在背包中可以选择该物品0到指定数量的次数。这是介于0-1背包(每个物品最多选一次)和完全背包(每个物品可选无限次)之间的情况。
例如,给定n个物品,每个物品有重量w[i]、价值v[i]和数量c[i],以及一个容量为V的背包,要求选择物品的总重量不超过V的前提下,使得总价值最大。
第二步:双重背包的基本解法
基本思路是将每个物品拆分成c[i]个独立的0-1背包物品。具体做法如下:
1. 遍历每个物品i
2. 将物品i拆分成c[i]个完全相同的物品,每个重量为w[i],价值为v[i]
3. 对拆分后的物品进行0-1背包处理
伪代码如下:
```
for i = 1 to n:
for j = 1 to c[i]:
for k = V downto w[i]:
dp[k] = max(dp[k], dp[k-w[i]] + v[i])
```
这种方法的时间复杂度为O(V*∑c[i]),当c[i]较大时效率较低。
第三步:优化双重背包的解法
优化思路是利用二进制拆分,将每个物品拆分成O(log c[i])个物品。具体做法如下:
1. 对于每个物品i,将其数量c[i]拆成若干个2的幂次方的和
2. 对拆分后的物品进行0-1背包处理
例如,如果c[i]=13,可以拆分为1, 2, 4, 6(或者1, 2, 4, 8,但8>13/2,所以用6)
伪代码如下:
```
for i = 1 to n:
k = 1
while k <= c[i]:
w = k * w[i]
v = k * v[i]
for j = V downto w:
dp[j] = max(dp[j], dp[j-w] + v)
c[i] -= k
k *= 2
if c[i] > 0:
w = c[i] * w[i]
v = c[i] * v[i]
for j = V downto w:
dp[j] = max(dp[j], dp[j-w] + v)
```
这种方法的时间复杂度为O(V*∑log c[i]),效率更高。
第四步:双重背包的代码实现
以下是C++语言的完整实现:
```cpp
#include
#include
using namespace std;
int main() {
int n, V;
cin >> n >> V;
int w[n+1], v[n+1], c[n+1];
for (int i = 1; i <= n; i++) {
cin >> w[i] >> v[i] >> c[i];
}
int dp[V+1];
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++) {
int k = 1;
while (k <= c[i]) {
int weight = k * w[i];
int value = k * v[i];
for (int j = V; j >= weight; j--) {
dp[j] = max(dp[j], dp[j-weight] + value);
}
c[i] -= k;
k *= 2;
}
if (c[i] > 0) {
int weight = c[i] * w[i];
int value = c[i] * v[i];
for (int j = V; j >= weight; j--) {
dp[j] = max(dp[j], dp[j-weight] + value);
}
}
}
cout << dp[V] << endl;
return 0;
}
```
第五步:经典例题分析
以Codeforces上的"Knapsack"问题为例:
题目描述:给定n个物品,每个物品有重量w[i]、价值v[i]和数量c[i],以及一个容量为V的背包,求最大价值。
解题思路:
1. 使用二进制拆分优化双重背包
2. 对于每个物品,先拆分成2的幂次方组合
3. 最后处理剩余部分
4. 使用一维数组进行0-1背包处理
注意点:
- 拆分时k从1开始,每次乘以2
- 处理剩余部分时,c[i]可能已经小于k
- 倒序遍历背包容量,保证每个物品只被处理一次
第六步:练习与提高
为了掌握双重背包问题,建议:
1. 先实现基本的双重背包解法
2. 再实现优化后的二进制拆分解法
3. 尝试将双重背包与其他算法结合使用
4. 解决至少5-10道双重背包相关题目
推荐练习题:
- Codeforces 237A. Free Cash
- Codeforces 1187D. Subarray Sorting
- Codeforces 526C. 2D Traveling
- Codeforces 1157D. N Problems in a Contest
常见坑提醒
1. 时间复杂度计算错误:二进制拆分的时间复杂度是O(V*∑log c[i]),不是O(V*∑c[i])
2. 物品数量处理不当:在二进制拆分后,不要忘记处理剩余部分
3. 边界条件考虑不周:当V=0或所有物品重量都大于V时的特殊情况
4. 空间优化时的错误:在使用一维数组时,必须倒序遍历背包容量
5. 数据溢出:当物品数量和价值较大时,注意使用long long类型
6. 数组初始化错误:dp数组应初始化为0,表示初始状态下价值为0
掌握双重背包问题不仅能解决多种背包变种问题,还能培养算法思维和优化能力,是编程竞赛中的重要知识点。

