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[i−1][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[i−1][j−ci]+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[i−1][j]max(dp[i−1][j],dp[i−1][j−ci]+wi)if j<ci (放不下)if j≥ci (能放下)
【边界条件】 : 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[i−1][j−c[i]]和 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]的影响,如果逆序枚举j,则当求出 d p [ i ] [ j ] dp[i][j] dp[i][j]的结果可直接覆盖到 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]的位置。因为 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][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[i−1][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[i−1][j−k∗ci]+k∗wi),0≤k≤ni且j−k∗ci≥0
【边界条件】:
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(V∑ni)
- 空间复杂度为 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[i−1][j]、 d p [ i − 1 ] [ j − c i ] dp[i-1][j-c_i] dp[i−1][j−ci]、 d p [ i − 1 ] [ j − 2 ∗ c i ] dp[i-1][j-2*c_i] dp[i−1][j−2∗ci]等状态的影响,因此仍然可以逆序枚举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(V∑ni)
- 空间复杂度为 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=0k2i≤ni 的最大整数,
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=ni−∑i=0k2i=ni−(2k+1−1)。
论证:从 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 这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 20,21,22,......2k 这k+1个数中,选出若干个相加,可以表示出 0 0 0 ~ 2 k + 1 − 1 2^{k+1}-1 2k+1−1 之间的任意整数。
- 根据 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} 20,21,22,......2k,2k+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+1−1,则从 2 0 , 2 1 , 2 2 , . . . . . . 2 k 2^0,2^1,2^2,......2^k 20,21,22,......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 20,21,22,......2k,Ri 这k+2个数中,选出若干个相加,可以表示出 0 0 0 ~ n i n_i ni 之间的任意整数。
时间复杂度降为 O ( V ∑ l o g n ) O(V\sum logn) O(V∑logn) 。
【伪代码】
//二进制拆分
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][j−k∗ci]+wi,dp[i][j]),j−k∗ci≥0
【边界条件】:
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]+2∗3 ,
d
p
[
1
]
[
0
]
+
3
∗
3
dp[1][0]+3*3
dp[1][0]+3∗3}
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]+2∗3 }
所以 :
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][j−c[i]]+wi,
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][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[i−1][j]和 d p [ i ] [ j − c [ i ] ] dp[i][j-c[i]] dp[i][j−c[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)