【C++算法】01背包、完全背包、多重背包、二维消费背包详解与模板代码

本文详细介绍了背包问题的几种动态规划解决方案,包括0-1背包、完全背包、一维数组与二维数组的解法,以及针对不同约束条件(如多重背包和二进制优化)的扩展。着重讨论了时间复杂度和空间复杂度,并提供了代码示例。
摘要由CSDN通过智能技术生成

背包问题

0-1背包

例题:信奥一本通 1267

请添加图片描述

二维数组解法

时间复杂度O(n²),空间复杂度O(n²)

状态转移方程怎么来的已经非常熟悉了,如果j<w[i]即当前背包容量不够放下物品i,那么就是上一个状态的最大值;如果j>=w[i]就是说放得下了,有两种决策:拿或者不拿,如果不拿就是上一种状态,同上,如果拿了,背包就要空w[i]的空间出来,价值再加上c[i],注:容易MLE。

#include<bits/stdc++.h>
using namespace std;
const int maxn=30+1;
const int maxm=200+2;
int dp[maxn][maxm];
int w[maxn]; // 重量
int c[maxn]; // 价值
int main() {
	int m,n;
	cin>>m>>n; // m:背包容量,n:物品数
	for(int i=1;i<=n;i++) {
		cin>>w[i]>>c[i];	
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(j<w[i])
				dp[i][j]=dp[i-1][j];
			else
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+c[i]);
		}
	}
	// 输出dp表看看变化
	for(int i=0;i<=n;i++) {
		for(int j=0;j<=m;j++) {
			cout<<dp[i][j]<<' ';
		}
		cout<<endl;
	}
	cout<<dp[n][m];
	return 0;
}
一维数组解法

时间复杂度O(n²),空间复杂度O(n)

在动态规划中有个原则:后无效性原则,即当前状态只与上一个状态有关,而一维数组刚好可以满足这个特性,只需要找到dp[i]和dp[i-1]之间的状态转移方程即可。

当背包容量和最大物品数多起来过后,使用二维数组必定MLE(爆内存),所以需要压缩到一维数组。

一维数组只需要注意一个地方:为什么对于容量j是逆推?如果正向顺序更新,那么当前循环中计算dp[j]时,dp[j-w[i]]可能已经被更新,这样就会造成错误的结果。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5;
const int maxm=11;
int w[maxn]; // 重量
int c[maxn]; // 价值
int dp[maxm]; // 滚动数组
int main() {
	int m,n;
	cin>>m>>n; // m:背包容量,n:物品数
	for(int i=1;i<=n;i++) {
		cin>>w[i]>>c[i];	
	}
	for(int i=1;i<=n;i++) {
		for(int j=m;j>=1;j--) {
			// 如果够,判断拿不拿
			if(j>=w[i])
				dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
		}
		// 每次赋值完后打印一遍这一行的数据
		for(int j=1;j<=m;j++) {
			cout<<dp[j]<<' ';
		}
		cout<<endl;
	}
	cout<<dp[m];
	return 0;
}
完全背包

例题 信奥一本通 P1268

在这里插入图片描述

朴素法

时间复杂度 O(n³),空间复杂度O(n)

再设置一个遍历因子k,在一维01背包的基础上选择拿k个当前物品。

#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
	cin>>m>>n;
	for(int i=1;i<=n;i++) {
		cin>>w[i]>>c[i];
	}
	for(int i=1;i<=n;i++) {
		for(int j=m;j>=1;j--) {
			// k最少拿0间,最多拿j/w[i]件
			for(int k=0;k<=j/w[i];k++) {
				if(j>=w[i]) {
					dp[j]=max(dp[j],dp[j-k*w[i]]+k*c[i]);
				}
			}
		}	
	}
	cout<<"max="<<dp[m]<<endl;
	return 0;
}
二维数组

时间复杂度O(n²),空间复杂度O(n²)

可以理解为dp[i] [j]的最优解来自与上一行的最优解或者当前行以前的最优解+新增的一个物品价值

#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxn][maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
	cin>>m>>n;
	for(int i=1;i<=n;i++) {
		cin>>w[i]>>c[i];
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(j>=w[i]) 
				// 要看当前行的当前最优解
				dp[i][j]=max(dp[i-1][j],dp[i][j-w[i]]+c[i]);
			else
				dp[i][j]=dp[i-1][j];
		}	
	}
	cout<<"max="<<dp[n][m]<<endl;
	return 0;
}
一维数组

时间复杂度O(n²),空间复杂度O(n)

因为dp[i] [j]的最优解可能来自于当前行最优解+一个物品的价值,所以需要顺推。

#include<bits/stdc++.h>
using namespace std;
const int maxn=30+3;
const int maxm=200+2;
int m,n; // 背包容量和物品数
int dp[maxm];
int w[maxn]; // 存储每个物品的重量
int c[maxn]; // 存储每个物品的价值
int main() {
	cin>>m>>n;
	for(int i=1;i<=n;i++) {
		cin>>w[i]>>c[i];
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			if(j>=w[i])
				dp[j]=max(dp[j],dp[j-w[i]]+c[i]);
		}
	}
	cout<<"max="<<dp[m]<<endl;
	return 0;
}
多重背包
朴素法

时间复杂度O(n³),空间复杂度O(n)

#include<bits/stdc++.h>
using namespace std;
const int maxn=500+5;
const int maxm=6000+6;
int v[maxn]; // 价格
int w[maxn]; // 价值
int s[maxn]; // 最大数量
int dp[maxm]; // 朴素法一维数组
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++) {
		for(int j=m;j>=1;j--) {
			for(int k=0;k<=s[i];k++) {
				if(j>=k*v[i])
					dp[j]=max(dp[j],dp[j-v[i]*k]+k*w[i]);
			}
		}
	}
	cout<<dp[m];
	return 0;
}
二进制优化

即限制条件有两个,之前的背包是在物品总体积不超过背包容量的基础上使物品总价值最高,现在是:总体积不超过背包容量,总重量不超过背包可承受的最大重量。

二维费用背包

例题:洛谷 P1507

即限制条件有两个,之前的背包是在物品总体积不超过背包容量的基础上使物品总价值最高,现在是:总体积不超过背包容量,总重量不超过背包可承受的最大重量。

三维数组解法

时间复杂度O(n²),空间复杂度O(n³)

image-20230814174805663

无非就是多了一个代价,所以两个条件同时满足的时候才可选取,状态转移方程的变化也异曲同工。

#include<bits/stdc++.h>
using namespace std;
const int maxh=400+4; // 体积最大值
const int maxt=400+4; // 质量最大值
const int maxn=50+5; // 物品最大数量
int h,t,n;
int dp[maxn][maxh][maxt];
int hh[maxn]; // 体积
int tt[maxn]; // 质量
int kk[maxn]; // 价值
int main() {
	cin>>h>>t;
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>hh[i]>>tt[i]>>kk[i];
	}
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=h;j++) {
			for(int k=1;k<=t;k++) {
				// j代表当前体积,k代表当前质量
				if(j>=hh[i] && k>=tt[i]) {
					dp[i][j][k]=max(dp[i-1][j][k],dp[i-1][j-hh[i]][k-tt[i]]+kk[i]);
				} else {
					dp[i][j][k]=dp[i-1][j][k];
				}
			}
		}	
	}
	cout<<dp[n][h][t];
	return 0;
}
二维数组解法

时间复杂度O(n³),空间复杂度O(n²)

image-20230814175220275

利用压缩数组进行转移的时候,依然要逆向进行。变化不大。

#include<bits/stdc++.h>
using namespace std;
const int maxh=400+4; // 体积最大值
const int maxt=400+4; // 质量最大值
const int maxn=50+5; // 物品最大数量
int h,t,n;
int dp[maxh][maxt];
int hh[maxn]; // 体积
int tt[maxn]; // 质量
int kk[maxn]; // 价值
int main() {
	cin>>h>>t;
	cin>>n;
	for(int i=1;i<=n;i++) {
		cin>>hh[i]>>tt[i]>>kk[i];
	}
	for(int i=1;i<=n;i++) {
		for(int j=h;j>=1;j--) {
			for(int k=t;k>=1;k--) {
				// j代表当前体积,k代表当前质量
				if(j>=hh[i] && k>=tt[i]) {
					dp[j][k]=max(dp[j][k],dp[j-hh[i]][k-tt[i]]+kk[i]);
				} 
			}
		}	
	}
	cout<<dp[h][t];
	return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值