01背包、多重背包与完全背包

本文详细介绍了01背包、多重背包、完全背包问题的解题思路与优化方法,包括状态定义、状态转移、空间优化,以及二进制优化策略,旨在通过动态规划解决在体积约束下物品选择以最大化价值的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

01背包

问题描述

给定 N N N种物品,每种物品只有1件。已知第 i i i件物品的体积是 c i c_i ci, 价值是 w i w_i wi
请你选出若干物品,在体积之和不超过 V V V的条件下,使价值总和最大。

解题思路

【状态定义】 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i个物品,在总体积不超过 j j j的情况下,所获得的的最大价值为 d p [ i ] [ j ] dp[i][j] dp[i][j]

【 状态转移】
对于第 i i i种物品,有两种情况

  • 不放入,则: d p [ i ] [ j ] dp[i][j] dp[i][j] = d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]
  • 放入,则: d p [ i ] [ j ] dp[i][j] dp[i][j] = d p [ i − 1 ] [ j − c i ] + w i dp[i-1][j-c_i] + w_i dp[i1][jci]+wi

是否放入第 i i i件物品,需要进行决策,决策的原则是:使价值最大化以及是否能放得下。因此:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j ] if  j < c i  (放不下) m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − c i ] + w i ) if  j ≥ c i  (能放下) dp[i][j]= \begin{cases} dp[i-1][j] & \text{if } j < c_i \text{ (放不下)} \\ max(dp[i-1][j], dp[i-1][j-c_i] + w_i)& \text{if } j ≥ c_i \text{ (能放下)} \end{cases} dp[i][j]={dp[i1][j]max(dp[i1][j],dp[i1][jci]+wi)if j<ci (放不下)if jci (能放下)

【边界条件】 d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0 , i = 0 i =0 i=0

【伪代码】

for(int i = 1;i<=N;i++){
	for(int j = 0;j<=V;j++){
		if(j >= c[i]) dp[i][j] = max(dp[i-1)[j], dp[i-1][j-c[i]+w[i]);
		else dp[i][j] = dp[i-1][j];
	}
}

【性能分析】

  • 时间复杂度 O ( N V ) O(NV) O(NV)
  • 空间复杂度 O ( N V ) O(NV) O(NV)
空间优化

在这里插入图片描述

由上图可知, d p [ i ] [ j ] dp[i][j] dp[i][j]只受 d p [ i − 1 ] [ j − c [ i ] ] dp[i-1][j-c[i]] dp[i1][jc[i]] d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]的影响,如果逆序枚举j,则当求出 d p [ i ] [ j ] dp[i][j] dp[i][j]的结果可直接覆盖到 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]的位置。因为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]只能被用来更新 d p [ i ] [ j ] dp[i][j] dp[i][j] d p [ i ] [ j + c [ i ] ] dp[i][j+c[i]] dp[i][j+c[i]],当求 d p [ i ] [ j ] dp[i][j] dp[i][j]时,这两个值已经被更新了, d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]已没有用了。

【伪代码】

    for (int i = 1; i <= N; i++) {
        for(int j = V; j>=c[i];j--){  //j<c[i]时,dp[i][j] = dp[i-1][j], 不需要被更新
            dp[j] = max(dp[j-c[i]] + w[i],dp[j]);
        }
    }

【性能分析】

  • 时间复杂度 O ( N V ) O(NV) O(NV)
  • 空间复杂度 O ( V ) O(V) O(V)

———————————————————————————————————————————————————————

多重背包

问题描述

给定 N N N种物品,每种物品有多件,为 n i n_i ni 。已知第 i i i件物品的体积是 c i c_i ci, 价值是 w i w_i wi
请你选出若干物品,在体积之和不超过 V V V的条件下,使价值总和最大。

解题思路

【状态定义】 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个物品,在总体积不超过 j j j的情况下,所获得的的最大价值为 d p [ i ] [ j ] dp[i][j] dp[i][j]

【 状态转移】
对于第 i i i种物品,枚举选择的数量 k k k,当 k k k为0时为不选。决策的原则仍然是:使价值最大化以及是否能放得下,因此:
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j − k ∗ c i ] + k ∗ w i ) , 0 ≤ k ≤ n i 且 j − k ∗ c i ≥ 0 dp[i][j] = max(dp[i-1][j-k*c_i] +k* w_i) , 0≤k≤ n_i 且 j-k*c_i ≥0 dp[i][j]=max(dp[i1][jkci]+kwi),0knijkci0

【边界条件】 d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0 , i = 0 i =0 i=0
【伪代码】

for(int i = 1;i<=N;i++){
	for(int j = 0;j<=V;j++){
		for(int k = 0;k<=n[i];k++){
			if(j >= k*c[i]) dp[i][j] = max(dp[i][j], dp[i-1][j-k*c[i] + k*w[i]);
		}
	}
}

【性能分析】

  • 时间复杂度为 O ( V ∑ n i ) O(V\sum{n_i}) O(Vni)
  • 空间复杂度为 O ( N V ) O(NV) O(NV)
空间优化

在这里插入图片描述

和01背包一样, d p [ i ] [ j ] dp[i][j] dp[i][j] 只受 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] d p [ i − 1 ] [ j − c i ] dp[i-1][j-c_i] dp[i1][jci] d p [ i − 1 ] [ j − 2 ∗ c i ] dp[i-1][j-2*c_i] dp[i1][j2ci]等状态的影响,因此仍然可以逆序枚举j,压缩空间至一维。

【伪代码】

for(int i = 1;i<=N;i++){
	for(int j = V;j>=0;j--){
		for(int k = 0;k<=n[i];k++){
			if(j >= k*c[i]) dp[j] = max(dp[j], dp[j-k*c[i] + k*w[i]);
		}
	}
}

【性能分析】

  • 时间复杂度为 O ( V ∑ n i ) O(V\sum{n_i}) O(Vni)
  • 空间复杂度为 O ( V ) O(V) O(V)
二进制优化

其实,我们可以把 n i n_i ni个物品逐个拆分,这样问题就会转化成01背包,但是时间复杂度并没有发生改变。
我们考虑另外的拆分方式:二进制拆分法
我们将 n i n_i ni 拆分成 2 0 + 2 1 + 2 2 + . . . . 2 k + R i 2^0+2^1+2^2+....2^k +R_i 20+21+22+....2k+Ri, 其中, p p p是满足 ∑ i = 0 k 2 i ≤ n i \sum_{i=0}^{k}2^i ≤ n_i i=0k2ini 的最大整数, R i = n i − ∑ i = 0 k 2 i = n i − ( 2 k + 1 − 1 ) R_i = n_i-\sum_{i=0}^{k}2^i = n_i - (2^{k+1} -1) Ri=nii=0k2i=ni(2k+11)

论证:从 2 0 , 2 1 , 2 2 , . . . . . . 2 k , R i 2^0,2^1,2^2,......2^k,R_i 202122......2kRi 这k+2个数中,选出若干个相加,可以表示出 0 0 0 ~ n i n_i ni 之间的任意整数。

  • 2 0 , 2 1 , 2 2 , . . . . . . 2 k 2^0,2^1,2^2,......2^k 202122......2k 这k+1个数中,选出若干个相加,可以表示出 0 0 0 ~ 2 k + 1 − 1 2^{k+1}-1 2k+11 之间的任意整数。
  • 根据 k k k的最大性, 2 0 , 2 1 , 2 2 , . . . . . . 2 k , 2 k + 1 2^0,2^1,2^2,......2^k ,2^{k+1} 202122......2k2k+1 > n i n_i ni,则 R i R_i Ri < 2 k + 1 2^{k+1} 2k+1,即 R i R_i Ri 2 k + 1 − 1 2^{k+1}-1 2k+11,则从 2 0 , 2 1 , 2 2 , . . . . . . 2 k 2^0,2^1,2^2,......2^k 202122......2k 这k+1个数中,选出若干个相加,可以表示出 0 0 0 ~ R i R_i Ri 之间的任意整数。
  • 2 0 , 2 1 , 2 2 , . . . . . . 2 k , R i 2^0,2^1,2^2,......2^k,R_i 202122......2kRi 这k+2个数中,选出若干个相加,可以表示出 0 0 0 ~ n i n_i ni 之间的任意整数。

时间复杂度降为 O ( V ∑ l o g n ) O(V\sum logn) O(Vlogn)

【伪代码】

//二进制拆分
int sz = 0;
for(int i = 1;i<=N;i++){
	int k;
	for(k = 0;n[i]-(1<<(k+1)-1) > 0;k++){
		newc[sz] = (1 << k) * c[i];
		neww[sz] = (1 << k)* w[i]
		sz++;
	}
	int ri = n[i]-(1<<k-1);
	if(ri!=0){
		newc[sz] = ri*c[i];
		neww[sz] = ri*w[i];
		sz++;
	}
}
//01背包
for(int i=0;i<sz;i++){
	for(int j = V;j>=newc[i];j--){
		dp[j] = max(dp[j],dp[j-newc[i]]+neww[i]);
	}
}

完全背包

问题描述

给定 N N N种物品,每种物品有无限件 。已知第 i i i件物品的体积是 c i c_i ci, 价值是 w i w_i wi
请你选出若干物品,在体积之和不超过 V V V 的条件下,使价值总和最大。

解题思路

【状态定义】 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i个物品,在总体积不超过 j j j的情况下,所获得的的最大价值为 d p [ i ] [ j ] dp[i][j] dp[i][j]
【 状态转移】
和多重背包类似,对于第 i i i种物品,枚举选择的数量 k k k,和多重背包不同的是,数量k只受背包容量的限制:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j − k ∗ c i ] + w i , d p [ i ] [ j ] ) , j − k ∗ c i ≥ 0 dp[i][j] = max(dp[i][j-k*c_i] + w_i, dp[i][j]) , j-k*c_i ≥0 dp[i][j]=max(dp[i][jkci]+wi,dp[i][j]),jkci0
【边界条件】 d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0 , i = 0 i =0 i=0
【伪代码】

for(int i = 1;i<=N;i++){
	for(int j = 0;j<=V;j++){
		for(int k=0;k*c[i] <= j;k++){
			dp[i][j] = max(dp[i][j],dp[i-1][j-k*c[i]]+k*w[i]);
		}
	}	
}
状态转移优化

在这里插入图片描述
如图:
d p [ 2 ] [ 6 ] dp[2][6] dp[2][6] = max{ d p [ 1 ] [ 6 ] dp[1][6] dp[1][6], d p [ 1 ] [ 4 ] dp[1][4] dp[1][4]+3, d p [ 1 ] [ 2 ] + 2 ∗ 3 dp[1][2]+2*3 dp[1][2]+23 , d p [ 1 ] [ 0 ] + 3 ∗ 3 dp[1][0]+3*3 dp[1][0]+33}
d p [ 2 ] [ 4 ] dp[2][4] dp[2][4] = max{ d p [ 1 ] [ 4 ] dp[1][4] dp[1][4], d p [ 1 ] [ 2 ] + 3 dp[1][2]+3 dp[1][2]+3, d p [ 1 ] [ 0 ] + 2 ∗ 3 dp[1][0]+2*3 dp[1][0]+23 }
所以 : d p [ 2 ] [ 6 ] dp[2][6] dp[2][6] = max( d p [ 2 ] [ 4 ] + 3 dp[2][4]+3 dp[2][4]+3, d p [ 1 ] [ 6 ] dp[1][6] dp[1][6] )

进一步可推: d p [ i ] [ j ] dp[i][j] dp[i][j] = max( d p [ i ] [ j − c [ i ] ] + w i dp[i][j-c[i]] + w_i dp[i][jc[i]]+wi, d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j])。
这样,就不必再枚举k了。

思考:为什么多重背包不能这样优化?

【伪代码】

for(int i = 1;i<=N;i++){
	for(int j = 0;j<=V;j++){
		if(j >= c[i]) dp[i][j] = max(dp[i-1][j],dp[i][j-c[i]);
		else dp[i][j] = dp[i-1][j];
	}
}

【性能分析】

  • 时间复杂度为 O ( N V ) O(NV) O(NV)
  • 空间复杂度为 O ( N V ) O(NV) O(NV)
空间优化

由于 d p [ i ] [ j ] dp[i][j] dp[i][j] 的状态值只受 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j] d p [ i ] [ j − c [ i ] ] dp[i][j-c[i]] dp[i][jc[i]] 两个状态值的影响,因此,可以将二维空间优化成一维。

【伪代码】

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

【性能分析】

  • 时间复杂度为 O ( N V ) O(NV) O(NV)
  • 空间复杂度为 O ( V ) O(V) O(V)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值