一. 题目
描述
小熊有 N 种物品和一个容量是 V 的背包。
物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出
输出一个整数,表示最大价值。
输入样例
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出样例
8
二. 解题思路
本题涉及到三种背包
-
0-1背包
-
多重背包
-
完全背包
多重背包
0-1背包实质上是一种特殊的多重背包, 而多重背包可以转化为 0-1背包问题来处理. 现在我们来介绍使用二进制优化的方法来解决多重背包.
所谓的二进制优化方法就是, 将一个体积为 v v v, 价值为 w w w, 数量为 s i si si 的"多重物品"按照2的次方来取出, 比如第一次取出1个, 第二次取出2个, 第三次取出4个… 最后一次取出 s i − 2 k si-2^k si−2k个. 然后将每次取出来的若干个物品当做同一个物品, 如第二次取出2个多重物品, 那就把它们当做一个体积为 2 ∗ v 2*v 2∗v, 价值为 2 ∗ w 2*w 2∗w, 数量为1的物品; 同理第k次取出的物品当做一个体积为 k ∗ v k*v k∗v, 价值为 k ∗ w k*w k∗w, 数量为1的物品. 这样, 多重背包问题就化为了 0-1 背包问题.
但是还需要注意的一点是, 为了确保多重背包问题中一个多重物品能放得下, 且不会多放, 所以我们在内部循环(循环容量)时, 应该从最大容量 V V V开始循环, 直到容量等于 k ∗ v k*v k∗v为止.
完全背包
完全背包问题比多重背包问题要简单, 因为少了数量的限制.
在完全背包问题中一个物品是否增加放入的数量只需要在循环容量时判断能不能加入, 加入后价值会不会更高即可.
状态转移方程:
b a g [ i ] = b a g [ i − v ] + w bag[i]=bag[i-v]+w bag[i]=bag[i−v]+w
只需要对每一个物品都用上述状态转移方程就可以不断更新bag获得最优解.
三. 代码实现
#include<iostream>
#define Max 1010
using namespace std;
int bag[Max];//背包问题都可以优化代码,只用一维数组来存储更新状态
//传统的二维数组也只需要用到当前行的上一行来更新数组,所以完全可以优化为一维数组
int n, V;
int main() {
int i;
cin >> n >> V;
for (i = 0; i < n; i++) {
int v, w, num,j;
cin >> v >> w >> num;
if (num == 0) {//完全背包
for (j = v; j <= V; j++)
bag[j] = max(bag[j], bag[j - v] + w );//一个物品的数量无脑往上加,只需要判断价值有没有更高即可
}
else {
if (num == -1)num = 1;//0-1背包问题是特殊的多重背包问题,直接化为多重背包来解决
for (int k = 1; k <= num; k *= 2)//采用二进制优化方法,每次取出2的次方个多重物品(每次都当做不同物品处理即可化为0-1背包)
{
for (j = V; j >= k*v; j--)//为了防止重复选择,只能从后往前遍历
bag[j] = max(bag[j], bag[j - k * v] + k * w);
num -= k;
}
if (num > 0) {
for (j = V; j >= num*v; j--) {//处理剩余的多重物品(因为物品的数量不总是2的次方)
bag[j] = max(bag[j], bag[j - num * v] + num * w);
}
}
}
}
cout << bag[V];
return 0;
}