这可能是困扰很多人很长时间的问题吧。
先把各个变量列出来
体积为 V V V的背包,有 n n n个物品,每个物品的体积为 w i w_i wi,价值为 v i v_i vi,每个物品装一次,求最大价值
来这看的肯定都是学习过基础背包的人,如果没有可以看我另一篇博客,里面有详细解释——点这里
下面先给出二维的转移方程
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j] = max(f[i - 1][j], f[i-1][j - w[i]] + v[i]) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i])
首先,对于二维数组的背包来说,正序和逆序是无所谓的,因为你把状态都保存了下来,而一维数组的背包是会覆盖之前的状态的
要想知道
f
[
i
]
[
j
]
f[i][j]
f[i][j],你要从
f
[
i
−
1
]
[
j
]
f[i - 1][j]
f[i−1][j]和
f
[
i
]
[
j
−
w
[
i
]
]
+
v
[
i
]
f[i][j - w[i]] + v[i]
f[i][j−w[i]]+v[i]两个状态转移而来,这两个状态可以直接从二维数组中取出
一维数组的转移方程
f [ j ] = m a x ( f [ j ] , f [ j − w [ i ] ] + v [ i ] ) f[j] = max(f[j], f[j - w[i]] + v[i]) f[j]=max(f[j],f[j−w[i]]+v[i])
f [ j ] f[j] f[j]表示在执行 i i i次循环后(此时已经处理 i i i个物品),前 i i i个物体放到容量 j j j的背包时的最大价值,即之前的 f [ i ] [ j ] f[i][j] f[i][j]。与二维相比较,它把第一维压去了,但是二者表达的含义是相同的,只不过 f [ j ] f[j] f[j]一直在重复使用,所以,也会出现第 i i i次循环覆盖第 i − 1 i-1 i−1次循环的结果。
按方程来说,其中有许多对应相等的关系,比如 f [ i − 1 ] [ j ] f[i - 1][j] f[i−1][j]和 f [ j ] f[j] f[j]就是相等的,这是为什么呢?求一下这几个值就好了
- 前
i
−
1
i-1
i−1个物品放到容量
j
j
j的背包中带来的收益(
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j])
由于在执行第 i i i次循环时, f [ j ] f[j] f[j]存储的是前 i i i个物体放到容量为 j j j的背包时的最大价值,在求前 i i i个物体放到容量 j j j时的最大价值(即之前的 f [ i ] [ j ] f[i][j] f[i][j])时,我们正在执行第 i i i次循环, f [ v ] f[v] f[v]的值还是在第 i − 1 i-1 i−1次循环时存下的值,在此时取出的 f [ j ] f[j ] f[j]就是前 i − 1 i-1 i−1个物体放到容量 j j j的背包时的最大价值,即 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]。 - 前
i
−
1
i-1
i−1件物品放到容量为
j
−
w
[
i
]
j-w[i]
j−w[i]的背包中带来的收益(
f
[
i
]
[
j
−
w
[
i
]
]
+
v
[
i
]
f[i][j-w[i]]+v[i]
f[i][j−w[i]]+v[i])
由于在执行第 i i i次循环前, f [ 0... V ] f[0...V] f[0...V]中保存的是第 i − 1 i-1 i−1次循环的结果,即是前 i − 1 i-1 i−1个物体分别放到容量 0... V 0...V 0...V时的最大价值,即 f [ i − 1 ] [ 0... V ] f[i-1][0...V] f[i−1][0...V],则在执行第 i i i次循环前, f f f数组中 j − w [ i ] j-w[i] j−w[i]的位置存储就是我们要找的前 i − 1 i-1 i−1件物品放到容量为 j − w [ i ] j-w[i] j−w[i]的背包中带来的收益 (即之前的 f [ i ] [ j − w [ i ] ] f[i][j-w[i]] f[i][j−w[i]])。
具体来说,由于在执行 j j j时,是还没执行到 j − w [ i ] j-w[i] j−w[i]的,因此, f [ j − w [ i ] ] f[j-w[i]] f[j−w[i]]保存的还是第 i − 1 i-1 i−1次循环的结果。即在执行第 i i i次循环且背包容量为 j j j时,此时的 f [ j ] f[j] f[j]存储的是 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j],此时 f [ j − w [ i ] ] f[j-w[i]] f[j−w[i]]存储的是 f [ i − 1 ] [ j − w [ i ] ] f[i-1][j-w[i]] f[i−1][j−w[i]]。
相反,如果在执行第 i i i次循环时,背包容量按照 0... V 0...V 0...V的顺序遍历一遍,来检测第 i i i件物品是否能放。此时在执行第 i i i次循环且背包容量为 j j j时,此时的 f [ j ] f[j] f[j]存储的是 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j],但是,此时 f [ j − w [ i ] ] f[j-w[i]] f[j−w[i]]存储的是 f [ i ] [ j − w [ i ] ] f[i][j-w[i]] f[i][j−w[i]]。
因为
j
>
j
−
w
[
i
]
j>j-w[i]
j>j−w[i],所以第
i
i
i次循环中,执行背包容量为
j
j
j时,容量为
j
−
w
[
i
]
j-w[i]
j−w[i]的背包已经计算过了,即
f
[
j
−
w
[
i
]
]
f[j-w[i]]
f[j−w[i]]中存储的是
f
[
i
]
[
j
−
w
[
i
]
]
f[i][j-w[i]]
f[i][j−w[i]]。它会从一开始就装入某个物品,只是为了价值最大,重复是肯定要存在的。即对于01背包,按照增序枚举背包容量是不对的。如下图
我们现在要求
i
=
2
i=2
i=2时的
f
[
5
]
f[5]
f[5]:
橙色的为数组现在存储的值,这些值是
i
=
1
i=1
i=1时(上一次循环)存入数组
f
f
f的。相当于
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]
而黄色的是我们要求的值,在求
f
[
5
]
f[5]
f[5]之前,
f
[
5
]
=
5
f[5]=5
f[5]=5,即
f
[
i
−
1
]
[
5
]
=
5
f[i-1][5]=5
f[i−1][5]=5
现在要求
i
=
2
i=2
i=2时的
f
[
5
]
=
f
[
5
−
2
]
+
10
=
5
+
10
=
15
>
f
[
i
−
1
]
[
5
]
=
5
f[5]=f[5-2]+10=5+10=15>f[i-1][5]=5
f[5]=f[5−2]+10=5+10=15>f[i−1][5]=5
故
f
[
5
]
=
15
f[5]=15
f[5]=15;
要注意在求
f
[
j
]
f[j]
f[j]时,它引用的
f
[
j
−
w
[
i
]
]
f[j-w[i]]
f[j−w[i]]和
f
[
j
]
f[j]
f[j]都是上一次循环的结果
我们现在要求
i
=
2
i=2
i=2时的
f
[
5
]
f[5]
f[5]
橙色为数组现在存储的值,这些值是
i
=
2
i=2
i=2时(本次循环)存入数组
f
f
f的。相当于
f
[
i
]
[
j
]
f[i][j]
f[i][j]。这是由于,我们是增序遍历数组
f
f
f的,在求
f
[
j
]
f[j]
f[j]时,
j
j
j之前的值
(
0...
v
−
1
)
(0...v-1)
(0...v−1)都已经在第
i
i
i次循环中求出。
黄色是我们要求的值,在求
f
[
5
]
f[5]
f[5]之前,
f
[
5
]
=
5
f[5]=5
f[5]=5,即
f
[
i
−
1
]
[
5
]
=
5
f[i-1][5]=5
f[i−1][5]=5
现在要求
i
=
2
i=2
i=2时的
f
[
5
]
=
f
[
5
−
2
]
+
10
=
10
+
10
=
20
>
f
[
i
−
1
]
[
5
]
=
5
f[5]=f[5-2]+10=10+10=20>f[i-1][5]=5
f[5]=f[5−2]+10=10+10=20>f[i−1][5]=5,故
f
[
5
]
=
20
f[5]=20
f[5]=20;
其中引用的
f
[
3
]
f[3]
f[3]是
f
[
i
]
[
3
]
f[i][3]
f[i][3]而不是
f
[
i
−
1
]
[
3
]
f[i-1][3]
f[i−1][3]
注意一点,在求
f
[
j
]
f[j]
f[j]时,它引用的
f
[
j
−
w
[
i
]
]
f[j-w[i]]
f[j−w[i]]是本次循环的结果,而
f
[
j
]
f[j]
f[j]是上一次循环的结果
I
n
In
In
o
t
h
e
r
other
other
w
o
r
d
s
words
words
在检测背包容量为
5
5
5时,看物品
2
2
2是否加入
由状态转移方程可知,我们的
f
[
5
]
f[5]
f[5]需要引用自己本身和
f
[
3
]
f[3]
f[3]
由于背包容量为
3
3
3时,可以装入物品
2
2
2,且收益比之前的大,所以放入背包了。
在检测
f
[
5
]
f[5]
f[5]时,肯定要加上物品
2
2
2的收益,而
f
[
5
]
f[5]
f[5]在引用
f
[
3
]
f[3]
f[3]时,
f
[
3
]
f[3]
f[3]时已经加过一次物品
2
2
2,
因此,在枚举背包容量时,物品
2
2
2加入了多次。
然后我们明确三个问题
- j − w [ i ] < j j-w[i]<j j−w[i]<j
- 状态 f [ i ] [ j ] f[i][j] f[i][j]是由 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]和 f [ i ] [ j − w [ i ] ] f[i][j-w[i]] f[i][j−w[i]]两个状态决定的
- 对于物品 i i i,我们在枚举背包容量时,只要背包容量能装下物品 i i i且收益比原来的大,就会成功放入物品 i i i
具体来说,枚举背包容量时,是以递增的顺序的话,由于
j
−
w
[
i
]
<
v
j-w[i]<v
j−w[i]<v,则会先计算
j
−
w
[
i
]
j-w[i]
j−w[i]。在背包容量为
j
−
w
[
i
]
j-w[i]
j−w[i]时,一旦装入了物品
i
i
i,由于求
f
[
j
]
f[j]
f[j]需要使用
f
[
i
−
1
]
[
j
−
w
[
i
]
]
f[i-1][j-w[i]]
f[i−1][j−w[i]],而若求
f
[
j
]
f[j]
f[j]时也可以装入物品
i
i
i的话,那么在背包容量为
v
v
v时,容量为
j
j
j的背包就装入可两次物品。又若
j
−
w
[
i
]
j-w[i]
j−w[i]是由之前的状态推出,它们也成功装入物品
i
i
i的话,那么容量为
j
j
j的背包就装入了多次物品
i
i
i了。
此时,在计算
f
[
j
]
f[j]
f[j]时,已经把物品
i
i
i能装入的全装入容量为
j
j
j的背包了,此时装入物品
i
i
i的次数一定是最大的
所以,顺序枚举容量是完全背包问题最简捷的解决方案。
图片来源及参考博客:
https://blog.youkuaiyun.com/c_circle/article/details/78804728
背包九讲