主要思路:
f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i]) (0=<k<=p)
与完全背包同==,只是k不是固定,所以没法优化掉k
根据当前的动态规划方程写代码:
// 多重背包 未优化前
#include <iostream>
#include <algorithm>
const int MAXN = 1005;
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int s[MAXN]; // 个数
int f[MAXN][MAXN]; // f[i][j], j重量下前i个物品的最大价值
int main(){
int n,m;
cin>>n>>m;
for(int i=1;i<n;i++) cin>>v[i]>>w[i]>>s[i];
// base case
f[0][..]=0;
f[..][0]=0;
for(int i=1; i<=n; i++){
for(int j=1; j<=m;j++){
for(int k=0; k*w[i]<=j&&k<=s[i];k++){
// k>=0开始 选0件、1件、2件
// 与完全背包不同的地方在于多了条件:s<=s[i]
f[i][j]=max(f[i-1][j-k*w[i]]+k*v[i]);
}
}
}
cout << f[n][m];
return 0;
}
优化的主要思想一:
- 去掉[i]维度,此时只j维度需要倒序:
因为 f[i][j]=max(f[i-1][j-k*v[i]]+k*w[i]) (0=<k<=p)
,类似0-1背包
代码如下:
// 多重背包 初级优化
// 这个优化版本还不能去掉k
// 因为你要考虑到物品的个数并不是无限
// 第一维:选择的物品循环
// 第二维:背包容量循环
// 第三维:选择的策略
#include <iostream>
#include <algorithm>
const int MAXN = 1005;
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int s[MAXN]; // 个数
int f[MAXN][MAXN]; // f[i][j], j重量下前i个物品的最大价值
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i < n; i++)
cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; i++)
{
// 同0-1背包的倒序
for (int j = m; j >= w[i]; j++)
{
for (int k = 0; k * w[i] <= j && k <= s[i]; k++)
{
// k>=0开始 选0件、1件、2件
// 与完全背包不同的地方在于多了条件:s<=s[i]
//f[i][j]=max(f[i-1][j-k*w[i]]+k*v[i]);
f[j] = max(f[j - k * w[i]] + k * v[i]); // 由于f[i-1][j-k*w[i]]->f[j-k*w[i]]所以要j倒序
}
}
}
cout << f[n][m];
return 0;
}
优化的主要思想二:
物品的数量,和每个物品的种类数增加,之前的优化一算法已经过不了这个题目
- 二进制拆分来优化,第i件物品比方有8件,我们选0/1/2/3/4/5/6/7/8件可以看成8件物品选或者不选,那么就是8个0-1背包问题了,这样拆复杂度还比较高
我们可以用二进制来拆,就是2^0, 2^1, 2^2 log8=3上取整
这三个组件作为基类
选0/1/2/3/4/5/6/7/8件可以看成8件物品选或者不选,可以由三个基类构成:
例如:0=0, 1=2^0, 2=2^1, 3=2^1+2^0 4=2^1+2^1
所以我们把这三个积累的0-1背包问题解出来选或者不选,8种背包再由这三类基础背包组成,就可以减少复杂度
# 关于拆分的代码:对于某一个种类的A吉他,数量有s个,二进制拆分也就数将s拆分
for(int k=1; k<=s; k*=2){
s-=k;
goods.push_back({v*k, w*k});
}
if(s>0){
goods.push_back({v*s, w*s});
}
// 然后再以goods中的good为单位选或者不选,就可以组合成0~8的各种选择了,如果是A吉他正好有8把
整个二进制优化代码如下:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N=2010;
int f[N],n,m;
struct good{
int w,v;
};
int main(){
cin>>n>>m;
vector<good> goods;
//good tmp;
// 二进制处理
for(int i=1; i<=n; i++){
int v,w,s; // 某一种商品的体积,价值,数量
// 对某一种商品进行二进制拆分
for(int k=1; k<=s; k*=2){
s-=k;
goods.push_back({k*w, k*v}); // 把每个基本类看成一个商品,所以要价格,体积要乘以k
}
if(s>0) goods.push_back({s*w, s*v});
}
// 二进制拆分后,每种商品拆成基本类,每个基本类看成一种商品,把所有商品的基本类组合起来goos,选或者不选每个基本类,转化成了0~1背包问题:选或者是不选每个基本类
// 第一层循环,物品类的选择
// 第一层选择的物品循环
// 第二层背包容量循环
// 第三层选择的策略
for(auto i:goods){
for(int j=m; j>=i.v;j--){
f[j]=max(f[j], f[j-i.v]+i.w); // i.v~v[i] i.w~w[i]
}
}
cout << f[m] << endl;
return 0;
}