多重背包问题(暴力、二进制优化、单调队列优化)

闫神视频笔记

视频链接

多重背包与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*wg[k]+(x-k)/v*w中的最大值,前者大,就说明q[tt]“大”;反之k“大”。而根据单调队列的思想,如果q[tt]k要小,则要去除q[tt],即tt–。
最后的多重背包问题III有点难理解,多看几遍就能看懂的。

猜你喜欢

转载自blog.csdn.net/lmmmmmmmmmmmmmmm/article/details/107174162
今日推荐