背包问题总结

这篇博客深入探讨了背包问题的三种类型:01背包、完全背包和多重背包,并提供了相应的求解方案。通过动态规划的方法,详细解释了每种背包问题的状态转移方程,同时给出了优化策略,如完全背包的二重循环优化和多重背包的二进制优化。此外,还介绍了混合背包的处理方式,即根据物品数量灵活运用不同类型的背包策略。
摘要由CSDN通过智能技术生成

本文涉及到的背包问题

  • 01背包。
  • 完全背包。
  • 多重背包。
  • 混合背包。
  • 01背包求解方案数。
  • 完全背包求解方案数。

01背包

01背包应该很初级了。给定一个容量为v的背包,有n个物品,它们的重量和价值分别为a和b,且每件物品只有一个,问如何选择物品来装入背包,以使在不超过背包容量上限的情况下得到最大的价值。我们直接使用这道例题:
在这里插入图片描述

  • 输入样例
70 3
71 100
69 1
1 2
  • 输出样例
3
  • AC Code
#include <iostream>
using namespace std;
const int maxn = 1005;
int m,t;
int a[maxn] = {0},b[maxn] = {0},f[maxn] = {0};
int main()
{
	cin>>t>>m;
	for(int i=1;i<=m;i++){
		cin>>a[i]>>b[i];
	}
	for(int i=1;i<=m;i++){
		for(int j=t;j-a[i]>=0;j--){
			f[j] = max(f[j],f[j-a[i]]+b[i]);
		}
	}
	cout<<f[t];
	return 0;
}

可以看到代码十分的简短,01背包的模板如下:

for(int i=1;i<=m;i++){
	for(int j=t;j-a[i]>=0;j--){
		f[j] = max(f[j],f[j-a[i]]+b[i]);
	}
}

用i来枚举每一件物品,用j来枚举背包中每一片空间。如果能放下,那么比较放和不放哪种情况得到的当前价值最大,以选择。状态转移方程:f[j] = max(f[j],f[j-a[i]]+b[i]);

01背包求解方案数

在这里插入图片描述

#include <iostream>
using namespace std;
const int maxn = 10005;
int n,m,f[maxn] = {0};
int main()
{
	cin>>n>>m;
	f[0] = 1;
	for(int i=1;i<=n;i++){
		int v;
		cin>>v;
		for(int j=m;j>=v;j--){
			f[j] += f[j-v];
		}
	}
	cout<<f[m];
	return 0;
 } 

非常基础的01背包问题,没什么好说的。

完全背包

完全背包和01背包的题头并没有很大的变化,只是每一件物品可以取无限次。此时,将代码略微修改,即可得到完全背包的模板:

for(int i=1;i<=m;i++){
	for(int j=a[i];j<=t;j++){
		f[j] = max(f[j],f[j-a[i]]+b[i]);
	}
}

👆可以看到,第二维变成for(int j=a[i];j<=t;j++),即,不是从背包的容量上限开始向下枚举,而是当前物品重量枚举到背包上限,这样可以确保尽可能多的物品放入背包。

完全背包求解方案数

拿一道例题来说明:
在这里插入图片描述
在这里插入图片描述

  • AC Code
#include <iostream>
#include <cstring>
using namespace std;
const int maxn = 25005;
int a[maxn] = {0},f[maxn] = {0};
int t;
int main()
{
	cin>>t;
	while(t--){
		memset(a,0,sizeof(a));
		memset(f,0,sizeof(f));
		int n,MAX = 0,ans = 0;
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			MAX = max(MAX,a[i]);
		}
		f[0] = 1;
		for(int i=1;i<=n;i++){
			for(int j=a[i];j<=MAX;j++){
				f[j] += f[j-a[i]];
			}
		}
		for(int i=1;i<=n;i++){
			if(f[a[i]] == 1)	ans++; 
		}
		cout<<ans<<endl;
	}
	return 0;
}
  • 解释:
    〇思路:找到另一个可以替代当前货币系统的货币系统,不是让我们构造一个全新的货币系统,而是将原来货币系统中冗余的面额去掉。什么称作冗余的面额?即货币系统中某个面额可以被其他货币表示出来时,称它是冗余的。因此,新的货币系统的货币数应该是只有一种表示方案的货币。
    ①数据结构:用a来记录每个货币的面额,用f来记录当前的面额有多少种表示方法。
    ②初始化:memset(a,0,sizeof(a));memset(f,0,sizeof(f));重复使用a和f,不将它们memset,第二次之后的结果一定出错。
    ③上限:即货币系统中面额的最大值。f[0] = 1也是初始化,即面额为0的表示方案也只有一种,那就是它自己。
    ④求解方案数及输出答案:
    👇同样是使用完全背包的思路。为什么使用完全背包?因为题目中表述:每种货币可以无限次使用。
for(int i=1;i<=n;i++){
	for(int j=a[i];j<=MAX;j++){
		f[j] += f[j-a[i]];
	}
}
for(int i=1;i<=n;i++){
	if(f[a[i]] == 1)	ans++; 
}

多重背包的二进制优化

多重背包不同于完全背包的“无限次使用”。多重背包指定了物品的个数,因此我们可以将多重背包看作是有多个物品的01背包,即,假设一件物品重量为v,价值为w,个数为s,我们可以将它看作是s个相同物品的01背包问题。但这样会使时间复杂度来到O(n×v×s),因此我们需要使用二进制来对多重背包进行优化。
我们直到,任何一个十进制数,都可以拓展为一个二进制数串,我们可以使用类似于快速幂的方法,来对多重背包进行优化。即,假设一件物品有13件,15可以拆分为1+4+8,那么这13件物品就变成了三捆,每捆分别有1、4、8个,对应的价值和重量也相应乘以1、4、8。
模板:

#include <iostream>
using namespace std;
const int maxn = 10005;
const int Lim = 105;
int v[maxn] = {0},w[maxn] = {0},f[maxn] = {0},n,m,cnt = 0;
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		int a,b,s;
		cin>>a>>b>>s;
		int k = 1;
		while(k<=s){
			v[++cnt] = k*a;
			w[cnt] = k*b;
			s -= k;
			k *= 2;
		}
		if(s){
			v[++cnt] = s*a;
			w[cnt] = s*b;
		}
	}
	for(int i=1;i<=cnt;i++){
		for(int j=m;j>=v[i];j--){
			f[j] = max(f[j],f[j-v[i]]+w[i]);
		}
	}
	cout<<f[m];
	return 0;
}

混合背包

明白了多重背包、完全背包和01背包,求解混合背包问题就不难了。我们拿一道模板题来说:
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
const int maxn = 1005;
int n,v;
int a[maxn] = {0},b[maxn] = {0},s[maxn] = {0},f[maxn] = {0};
int main()
{
	cin>>n>>v;
	for(int i=1;i<=n;i++){
		cin>>a[i]>>b[i]>>s[i];
	}
	for(int i=1;i<=n;i++){
		if(s[i] == 0){//完全背包 
			for(int j=a[i];j<=v;j++){
				f[j] = max(f[j],f[j-a[i]]+b[i]);
			}
		}
		else{//01背包与多重背包,都拆分成01背包来处理,多重背包用二进制优化 
			if(s[i] == -1)	s[i] = 1;
			for(int k=1;k<=s[i];k*=2){
				for(int j=v;j>=k*a[i];j--){
					f[j] = max(f[j],f[j-k*a[i]]+k*b[i]);
				}
				s[i] -= k;
			}
			if(s[i]){
				for(int j=v;j>=s[i]*a[i];j--){
					f[j] = max(f[j],f[j-s[i]*a[i]]+s[i]*b[i]);
				}
			}
		}
	}
	cout<<f[v];
	return 0;
}

分情况使用不同的背包,如果是多重背包,那么使用二进制优化将它转化为特殊的01背包,而01背包设置它的个数为1转化为只能使用一次的多重背包。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值