1.问题
有 N N N件物品和一个容量为 V V V的背包。放入第 i i i件物品消耗容量为 v i v_i vi,得到的价值为 w i w_i wi。求背包总价值最大值。
2.基本思路
问题特点:每个特点仅有一件,可以选择放或不放
用子问题定义状态:
F
[
i
,
v
]
F[i,v]
F[i,v]表示前
i
i
i个物品放入一个容量为
v
v
v的背包可以获得的最大价值。状态转移方程为:
F
[
i
,
v
]
=
m
a
x
{
F
[
i
−
1
,
v
]
,
F
[
i
−
1
,
v
−
C
i
]
+
W
i
}
(1)
F[i,v]=max\{F[i-1,v],F[i-1,v-C_i]+W_i\}\tag{1}
F[i,v]=max{F[i−1,v],F[i−1,v−Ci]+Wi}(1)
即对第i个物品有两种选择:
(1)不放入包中,则
F
[
i
,
v
]
=
F
[
i
−
1
,
v
]
F[i,v]=F[i-1,v]
F[i,v]=F[i−1,v];
(2)放入包中,则
F
[
i
,
v
]
=
F
[
i
−
1
,
v
−
C
i
]
+
W
i
F[i,v]=F[i-1,v-C_i]+W_i
F[i,v]=F[i−1,v−Ci]+Wi,即前
i
−
1
i-1
i−1个物品放入容量为
v
−
C
i
v-Ci
v−Ci的背包的最大价值加上第
i
i
i个物品的价值;
边界条件:
F
[
0
,
0...
V
]
=
0
F[0,0...V]=0
F[0,0...V]=0,即没有物品放入,获得的最大价值为零。
结果:
F
[
N
,
V
]
F[N,V]
F[N,V]。
遍历方向:由状态转移方程可知,
F
[
i
,
v
]
F[i,v]
F[i,v]与
F
[
i
−
1
,
v
]
F[i-1,v]
F[i−1,v]和
F
[
i
−
1
,
v
−
C
i
]
F[i-1,v-C_i]
F[i−1,v−Ci]有关,即与第
i
−
1
i-1
i−1行有关,因此遍历方向为从上到下。
伪代码:
F
[
0
,
0...
V
]
←
0
f
o
r
i
←
1
t
o
N
f
o
r
v
←
C
i
t
o
V
F
[
i
,
v
]
=
m
a
x
{
F
[
i
−
1
,
v
]
,
F
[
i
−
1
,
v
−
C
i
]
+
W
i
}
F[0,0...V]\leftarrow 0\\ for \ i \leftarrow 1 to \ \ N\\ \qquad \qquad for \ v \leftarrow C_i \ to \ V \\ \qquad \qquad \qquad \qquad\qquad\qquad\qquad\qquad\qquad\qquad F[i,v]=max\{F[i-1,v],F[i-1,v-C_i]+W_i\}
F[0,0...V]←0for i←1to Nfor v←Ci to VF[i,v]=max{F[i−1,v],F[i−1,v−Ci]+Wi}
时间复杂度:O(VN)
空间复杂度:O(VN)
3.空间复杂度优化
上面提到的变量方向是从上向下,那么左右呢?无论从左向右还是从右向左似乎都可以完成任务,但要进行空间优化,需要进一步分析。
(1)从左向右
若使用
F
[
i
,
C
i
]
F[i,C_i]
F[i,Ci]覆盖
F
[
i
−
1
,
C
i
]
F[i-1,C_i]
F[i−1,Ci],则当计算
F
[
i
,
2
C
i
]
F[i,2C_i]
F[i,2Ci]时,原来的
F
[
i
−
1
,
C
i
]
F[i-1,C_i]
F[i−1,Ci]被覆盖,会出现错误;
(2)从右向左
若从右向左更新,被更新的位置不会在本次遍历中再次使用,因此从右向左遍历可以优化空间复杂度。
伪代码:
F
[
0...
V
]
←
0
f
o
r
i
←
1
t
o
N
f
o
r
v
←
V
t
o
C
i
F
[
v
]
=
m
a
x
{
F
[
v
]
,
F
[
v
−
C
i
]
+
W
i
}
F[0...V]\leftarrow 0\\ for \ i \leftarrow 1 \ to \ \ N\\ \qquad \qquad for \ v \leftarrow V \ to \ C_i \\ \qquad \qquad \qquad \qquad\qquad\qquad\qquad\qquad F[v]=max\{F[v],F[v-C_i]+W_i\}
F[0...V]←0for i←1 to Nfor v←V to CiF[v]=max{F[v],F[v−Ci]+Wi}
时间复杂度:O(VN)
空间复杂度:O(V)
4.初始化细节
问题分为两种:
(1)背包恰好装满:
F
[
0
]
=
0
,
F
[
1...
N
]
=
−
∞
F[0]=0,F[1...N]=-\infin
F[0]=0,F[1...N]=−∞
(2)背包不用装满:
F
[
0...
V
]
=
0
F[0...V]=0
F[0...V]=0
如何理解:
回到二维空间考虑,
F
[
0
,
0...
V
]
F[0,0...V]
F[0,0...V]的含义是前0个物品装
0...
V
0...V
0...V容量背包能够获得的最大值。若要求恰好装满,除
F
[
0
,
0
]
F[0,0]
F[0,0]外,其余的
F
[
0
,
1...
V
]
F[0,1...V]
F[0,1...V]不满足“恰好”定义,背包价值没有意义;若不用装满,
F
[
0
,
0...
V
]
F[0,0...V]
F[0,0...V]可以什么都不装,最大价值为零。两者的区别在于边界条件是否有意义。
5.常数优化
内循环的下限可以进行优化:
f
o
r
v
←
V
t
o
C
i
⟶
f
o
r
v
←
V
t
o
m
a
x
(
V
−
∑
j
=
i
N
C
j
,
C
i
)
for \ v \leftarrow \ V \ to \ C_i \qquad\longrightarrow \qquad\qquad for \ v \leftarrow \ V \ to \ max(V- \sum\limits_{j=i}^{N}{C_j},C_i)
for v← V to Ci⟶for v← V to max(V−j=i∑NCj,Ci)
如何理解:
首先看原来的下限是如何得到的:
在内循环中,我们要保证装第
i
i
i个物品前背包至少有
C
i
C_i
Ci的余量,即背包容量的最小值为
C
i
C_i
Ci,这也就是内循环的下限。如果将其余的物品全部装完还至少有
C
i
C_i
Ci的余量,则可以将下限提升到装完其余物品后的余量
V
−
∑
j
=
i
N
C
i
V-\sum\limits_{j=i}^{N}{C_i}
V−j=i∑NCi。常数优化在
V
V
V较大时效果较好,可以利用前缀和快速计算求和,避免重复计算。
参考链接:
背包九讲