闫神视频笔记
视频链接
多重背包与01背包的差别就是多重背包每样物品的数量有指定数量的限制
多重背包问题I(题目链接)
解题思路:
本题的数据范围比较小,所以可以直接用最暴力的方法,即在01背包的基础上,遍历一下物品的个数
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 110;
int f[N];
int main(){
// freopen("1.txt","r",stdin);
int n,m,v,w,s;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v>>w>>s;
for(int j=m;j>=v;j--){
for(int k=0;k<=s&&k*v<=j;k++){
f[j] = max(f[j],f[j-v*k]+w*k);
}
}
}
cout<<f[m]<<endl;
return 0;
}
多重背包问题II(题目链接)
解题思路:
数据范围变成了 0<N≤1000,0<V≤2000,0<vi,wi,si≤2000 ,没法用原来暴力的方法。因此就需要用到二进制优化(没用过),所谓二进制优化就是将一个数字分成尽可能少的几个数(1,2,4,8…),以此将多重背包问题转化成01背包问题。在二进制优化的过程中,要保证分成的数的和要等于原来的数,否则会产生本来不存在的结果。因此最后一个数是原来的数减去已经分出的数的和的结果。分出的结果就变成01背包问题,用01背包的方法处理就行了。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 2010;
struct Good{
int v,w;
};
vector<Good>goods;
int f[N],n,m,v,w,s;
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
for(int i=0;i<n;i++){
cin>>v>>w>>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});
}
for(auto good:goods){
for(int i=m;i>=good.v;i--){
f[i] = max(f[i],f[i-good.v]+good.w);
}
}
cout<<f[m]<<endl;
return 0;
}
Dev C++ 无法运行auto?解决方法:让Dev C++支持C++11
多重背包问题III(题目链接)
解题思路:
这一题需要用到单调队列的优化,最好先看看单调队列(不难)。通过观察多重背包问题I的核心代码
#include<iostream>
using namespace std;
const int N = 110;
int f[N];
int main(){
// freopen("1.txt","r",stdin);
int n,m,v,w,s;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v>>w>>s;
//*******************核心代码*******************
for(int j=m;j>=v;j--){
//循环一
for(int k=0;k<=s&&k*v<=j;k++){
//循环二
f[j] = max(f[j],f[j-v*k]+w*k);
}
}
//*********************************************
}
cout<<f[m]<<endl;
return 0;
}
其中会包含一些冗余的重复计算。比如令m=100,v=2,k=3
当j=100时,需要用到f[100]、f[98]、f[96]、f[94],选择其中的最大值
当j=98时,需要用到f[98]、f[96]、f[94]、f[92],选择其中的最大值
当j=96时,需要用到f[96]、f[94]、f[92]、f[90],选择其中的最大值
可以发现和单调队列(滑动窗口)的思想完全一样。所以我们可以针对m对v的模(m%v) 来进行分类讨论。对于每个余数,运用单调队列的思想找出其最大值即可(比较值的大小不是直接比大小,看代码分析)。下面根据代码进行分析。
代码:
#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 20010;
int n,m;
int f[N],g[N],q[N];
int main(){
// freopen("1.txt","r",stdin);
cin>>n>>m;
for(int i=0;i<n;i++){
int v,w,s;
cin>>v>>w>>s;
//g数组保存的是上一轮的结果(即前i-1个物品在体积为0~m的最优解)
memcpy(g,f,sizeof f);
//分别讨论每个余数
for(int j=0;j<v;j++){
int hh=0,tt=-1;
for(int k=j;k<=m;k+=v){
f[k] = g[k];
//去除超出范围的数(即当前的体积-第i个物品最大可能的体积>队首的体积)
if(hh<=tt&&k-s*v>q[hh]) hh++;
//用最大的数去更新当前的最优解
//可以先看后面的代码,再看这一行,这样比较好理解
if(hh<=tt) f[k] = max(f[k],g[q[hh]]+(k-q[hh])/v*w);
//剔除队列里一定不会被用到的数
//闫神的代码,没看懂
// while(hh<=tt&&g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w) tt--;
//我的代码,转换一下就和闫神的一样了,至少我写的代码我看的懂一点
//观察多重背包问题I的核心代码,就能理解括号中的比较是怎么比大小的了
//实在看不懂,最后有讲解。
while(hh<=tt&&g[q[tt]]+(k-q[tt])/v*w<=g[k]) tt--;
//把当前数加到队列里去
q[++tt] = k;
}
}
}
cout<<f[m]<<endl;
return 0;
}
关于比较的讲解:有两个值,分别是q[tt]与k,怎么判断哪个更“大”呢?比如我要求体积为x的最优解(x%v,q[tt]%v,k%v三值相等),那么最优解就是g[q[tt]]+(x-q[tt])/v*w与g[k]+(x-k)/v*w中的最大值,前者大,就说明q[tt]“大”;反之k“大”。而根据单调队列的思想,如果q[tt]比k要小,则要去除q[tt],即tt–。
最后的多重背包问题III有点难理解,多看几遍就能看懂的。