个人做题笔记,仅供参考,如有错误并非巧合。
01背包、完全背包、多重背包的联系
01背包是最基础的背包问题,基本上只要学会01背包,完全背包和多重背包都能很容易理解。01背包的特点是物品只能用一次,完全背包则是能用无数次,多重背包可以用有限次。后两者都可以在01背包上进行简单的修改形成。
01背包
01分为二维和一维两个写法。当题目要求输出物品i时通常用二维数组。比如这题
二维dp[i][j]=max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])。
前者是不加物品i的情况,后者是加物品i的最佳情况。初始化需要注意的是,如果背包最大容量为baseweight,那么设置时要vector<vector<int>>dp(n,vector<int>(baseweight+1,0));如果物品i从1开始算,初始化只要全部化为0即可。如果物品从0开始算,初始化还要额外对i=0行多进行一步(因为i-1防止越界。相当于自己提前算一下第一行的数据):
for (int j = weight[0]; j <= bagweight; j++) {//bagweight背包最大容量
dp[0][j] = value[0];
}
一维dp[j]=max(dp[j],dp[j-weight[i]]+value[i]).
一维相当于是二维的简化版,省下了空间。把所有一维合起来就是二维。
值得注意的是,二维的遍历顺序可以替换,但是一维必须先遍历物品再遍历背包(因为背包必须逆序遍历,不能放在前面,否则dp[j]只会放入一个物品);二维可以正序遍历也可以逆序遍历,一维遍历背包时必须逆序遍历(因为物品只能放一次。i行是从i-1行得到的,如果从前往后覆盖,i行后面的数据从前面(也就是本层i行数据)得出,而非i-1行),不然会重复加上之前的物品。为了习惯,我选择全部先遍历物品且逆序遍历背包。
完全背包
前面说到一维数组遍历背包时由于会重复加上之前的物品,而不能正序遍历。那么在完全背包中,这是完全允许的,因为完全背包的物品有无限个!那么既然正序逆序无所谓,遍历顺序也可以替换了。
但这是在题目没要求凑成总和的元素的顺序关系上,如果要求了无序或有序(即求的是组合数或排列数),遍历顺序就有说法了。(稍微提一下,因为是求排列组合,会初始化dp[0]=1)
先下结论:
如果要求组合数,先遍历物品再遍历背包
如果要求排列数,先遍历背包再遍历物品
比如排列数题目里面的样例1。先遍历nums的话,就只有{1,1,2},不会再有{1,2,1},{2,1,1}的情况。因为是按照nums的顺序来的。而如果先遍历背包的话,nums会遍历很多次,就会出现样例中排列的情况。
多重背包
请看这道题,我的AC代码在最后
多重背包可以转换为01背包,只要把那些背包全部都一个个摊开,就变成01背包了!
方式有两种:
①直接摊开,在weight和value数组后面直接加上多个数的物品。但是此方法可能会超时。
②三层循环。添加第三层循环。
for (int k = 1; k <= nums[i] && (j - k * weight[i]) >= 0; k++) { // 遍历个数
dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]);
在01问题中,k相当于等于1,而在多重背包中,k可以等于很多数。
AC代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<vector>
using namespace std;
vector<int>nums(6);//个数
int weight[6]={1,2,3,5,10,20};
//vector<int>dp(1005);
bool b[1005]={false};
void bag(){
b[0]=true;
for(int i=0;i<6;i++){//砝码
for(int j=1000;j>=0;j--){//重量
for(int k=1;k<=nums[i]&&j+k*weight[i]<=1000;k++){//个数
if(b[j]){
b[j+k*weight[i]]=true;
}
}
}
}
int total=0;
for(int i=1;i<=1000;i++){
if(b[i]){
total++;
}
}
cout<<"Total="<<total;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
for(auto &i:nums){
cin>>i;
}
bag();
return 0;
}