背包问题详解

目录

多阶段动态规划问题

01背包问题

完全背包问题

多阶段动态规划问题

有一类动态规划可解的问题:它可以描述成若干个有序的阶段,且每个阶段的状态只和上一阶段的状态有关,一般吧这类问题成为多阶段动态规划问题。

01背包问题

01背包问题是这样的:有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大,其中每种物品都只有1件。

针对这个问题,令dp[i][v]表示前i件物品恰好装入容量为v的背包中所能获得的最大价值。

考虑对第i件物品的选择策略,有两种策略:

(1)不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,也即dp[i-1][v]

(2)放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i]

由于只有两种策略,且要求获得最大价值,因此:

dp[i][v]=max\left ( dp[i-1][v],dp[i-1][v-w[i]]+c[i] \right ) 

上面这个就是状态转移方程,注意到dp[i][v]只与前面的状态dp[i-1][]有关,所以可以枚举i从1到n,v到0到v,通过边界dp[0][v]=0(即前0件物品放入任何容量为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的是恰好为v的背包中都只能获得价值0)就可以把整个dp数组递推出来。而由于dp[i][v]表示的是恰好为v的情况,所以需要枚举dp[n][v],取其最大值才是最后的结果。

for(int i=1;i<=n;i++){
	for(int v=w[i];v<=V;v++){
		dp[i][v]=max(dp[i-1][v],dp[i-1][v-w[i]]+c[i]);
	}
}

时间复杂度和空间复杂度都是O\left ( nV \right ).

注意到状态转移方程中计算dp[i][v]时总是只需要dp[i-1][v]左侧部分的数据,且当计算dp[i+1][]的部分时,dp[i-1]的数据又完全用不到了(只需要用到dp[i][]),因此不妨可以直接开一个一维数组dp[v](即把一维省去),枚举方向改变为i从1到n,v从V到0,这样状态转移方程改变为: 

dp[v]=max\left ( dp[v],dp[v-w[i]]+c[i] \right )

这样修改对应到图中可以这样理解:v的枚举顺序变为从右到左,dp[i][v]右边的部分为刚计算过的需要保存给下一行使用的数据,而dp[i][v]左上角的阴影部分为当前需要使用的部分。 将这两者结合一下,即把dp[i][v]左上角和右边的部分放在一个数组里,每计算出一个dp[i][v],就相当于把dp[i-][v]抹消,因为在后面的运算中dp[i-1][v]再也用不到了。这种技巧为滚动数组

for(int i=1;i<=n;i++){
	for(int v=V;v>=w[i];v--){
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
	}
}

这样01背包问题就可以使用一维数组来解决了,空间复杂度为O\left ( V \right )

特别说明:如果是用二维数组存放,v的枚举是顺序还是逆序都无所谓;如果使用一维数组存放,则v的枚举必须是逆序。

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100;
const int maxv=1000;
int w[maxn],c[maxn],dp[maxn];
int main(){
	int n,V;
	cin>>n>>V;
	for(int i=0;i<n;i++){
		cin>>w[i];
	}
	for(int i=0;i<n;i++){
		cin>>c[i];
	}
	for(int v=0;v<=V;v++){
		dp[v]=0;
	}
	for(int i=1;i<=n;i++){
		for(int v=V;v>=w[i];v--){
			dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
		}
	}
	int max=0;
	for(int v=0;v<=V;v++){
		if(dp[v]>max){
			max=dp[v];
		}
	}
	cout<<max<<endl;
	return 0;
}

对能够划分阶段的问题,都可以尝试把阶段作为状态的一维,这可以使我们更方便地得到满足无后效性的状态。如果当前涉及的状态不满足无后效性,那么不妨把状态进行升维,即增加一维或若干维来表示相应的信息,这样可能就满足无后效性了。

完全背包问题

完全背包问题的叙述如下:

有n种物品,每种物品的单件重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都有无穷件

可以看出,完全背包问题和01背包问题的唯一区别就在于:完全背包的物品数量每种有无穷件,选取物品时对同一种物品可以选1件、选2件…只要不超过容量V即可,而01背包的物品数量每种只有一件。

同样令dp[i][v]表示前i件物品恰好放入容量为v的背包中能获得的最大价值。

和01背包一样,完全背包问题的每种物品都有两种策略,但是也有不同点。对第i件物品来说:

(1)不放第i件物品,那么dp[i][v]=dp[i-1][v],这步跟01背包一样。

(2)放第i件物品,这里的处理和01背包有所不同,因为01背包的每个物品只能选择一个,因此选择放第i件物品就意味着必须转移到dp[i-1][v-w[i]]这个状态;但是完全背包不同,完全背包如果选择放第i件物品之后并不是转移到dp[i-1][v-w[i]],而是转移到dp[i][v-w[i]],这是因为每种物品可以放任意件,放了第i件物品后还可以继续放第i件物品,直到第二维的v-w[i]无法保持大于等于0为止。

由上面的分析可以写出状态转移方程:

dp[i][v]=max\left ( dp[i-1][v],dp[i][v-w[i]]+c[i] \right )

边界:dp[0][v]=0

而这个状态转移方程同样可以改写成一维形式,即状态转移方程:

dp[v]=max\left ( dp[v],dp[v-w[i]]+c[i] \right ) 

边界:dp[v]=0

写成一维形式后和01背包完全相同,唯一的区别在于这里v的枚举顺序是正向枚举,而01背包的一维形式中v必须是逆向枚举。完全背包的一维形式代码如下:

for(int i=1;i<=n;i++){
	for(int v=w[i];v<=V;v++){
		dp[v]=max(dp[v],dp[v-w[i]]+c[i]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值