前置知识
首先需要知道0-1背包,0-1背包大概就是对于一定数量的物品,每种物品只存在选与不选两种状态,而且每种物品只有1个,也就是说对于每种物品要么放进背包要么不放进背包两种情况,最后可以通过滚动数组的方式以O(n)的空间复杂度完成相关题目
多重背包的概念
多重背包就是对于每种物品都有多个,与0-1背包的区别仅仅是每种物品的数量存在多个,如图
解决思路
朴素的解决方法是将每种物品的数量展开,然后按照0-1背包的思路逐个遍历求解,最后就能得到最终结果,但这样的时间复杂度非常高,假如物品的数量非常多,全部展开再一个一个求解显然有点慢,那么应该怎么优化呢?
对于展开求解,实际是一个一个求解,如果上过数电课,这时应该很容易想到任何数字都可以用二进制来快速表示,比如5可以用4+1也就是2的0次方+2的2次方来表示,优化时也是使用了这个思想,也就是二进制优化
二进制优化
对于多重背包的二进制优化,并不是简单的像9=8+1这样直接转化为二进制(这个实际是状态转移方向),这也是因为每个物品的选取可以是1-9个,所以二进制优化的方向应该是用二进制表示1-9的任意一个数,比如9应该等于2的0次方+2的1次方+2的2次方+2,再加上二进制正好可以表示两种状态,也就是将多个物品优化成了n位二进制物品,所以这个方向也几乎是合理的
二进制优化后的状态转移
初学多重背包时总会钻个牛角尖,如对于背包容量为4,但是如何保证重量为1的物品选取了4个?
按照前面说的,5 = 1+2+2,看上去好像选不到4对不对?
在0-1背包中,先遍历物品再遍历背包,由一维数组从后往前遍历,尝试将每一个物品放入,这里也一样,将每个二进制表示看成一个物品,所以先求将1个物品放入,接着将2个物品放入,然后由于已经求出了放入一个物品的价值,这时候2个物品放入背包后的价值自然由1个物品放入背包状态转移得到
同理,背包容量为4要想放入4个物品自然是从之前已经放入2个物品的状态得到,这样就有一个状态转移链,即4->2->1
模板题
如果还是不理解,不妨试试这道模板题,做完再看看相信也能理解了
洛谷 P1776
C++代码
#include<bits/stdc++.h>
using namespace std;
int n,W;
int v[10000000];
int w[10000000];
int dp[10000000];
void getval(){
for(int i=1; i<=n; ++i){
for(int j=W; j>=w[i]; --j){
dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
}
}
}
int main(){
int tmpv, tmpw, m;
int k =0;
scanf("%d%d", &n, &W);
for(int i=1; i<=n; ++i){
scanf("%d%d%d", &tmpv, &tmpw, &m);
for(int j=1; j<=m; j<<=1){
k++;
v[k] = tmpv*j;
w[k] = tmpw*j;
m -= j;
}
if(m > 0){
k++;
v[k] = tmpv*m;
w[k] = tmpw*m;
}
}
n = k;
getval();
printf("%d", dp[W]);
return 0;
}
总结
- 多重背包可以转化为0-1背包求解,但由于时间复杂度的关系,需要优化,根据0-1背包的特点与状态转移的方向,可以用二进制优化数量
- 二进制优化目的是用二进制表示1-n个物品的数量,这样既能保证dp状态转移选取数量时可以覆盖1-n的任何数量的物品,也加快了选取的速度
后话
- 由于本人学识浅薄,仅仅只是从程序的角度去思考多重背包,实际上多重背包的优化涉及到数学知识,如有兴趣可以参考其他资料
- 文章中如有错误,希望各位大佬能在评论区指出