【算法复习】背包问题 经典动态规划
其实我大概想法是想写一个动态规划的专题,然后里面可能有一些小专题,所以如果我没有鸽掉的话,这篇的链接会放入DP专题里面
这里大概就写写常见的背包问题吧。
01背包
可以说是最经典的背包问题了,有\(n\)个物品, 每个物品有一个价值\(vi\)和一个体积\(wi\),背包的最多容纳体积之和为\(m\)的物体。
现在让你求背包可以放入的最高价值之和。
我们考虑令\(dp[i][j]\)表示考虑到第i个物品,现在已经占用的背包容积是j的最优价值,那么我们有转移方程:
\(dp[i][j] = max\{dp[i - 1][j - w[i]] + v[i]\}\)
伪代码:
for i 1 to n
for j 1 to m
dp[i][j] = max{dp[i - 1][j - w[i]] + v[i]}
滚动数组优化
可以发现\(dp[i]\)一定从\(dp[i - 1]\)转移,而且我们只需要最后的dp[n],也就是考虑完所有物品的数组.所以我们并不需要保留所有的dp数组,只需要保留上一组的信息就可以算出下一组,
因此我们令\(f[i]\)表示已经占用的背包容积是j的最优价值。
伪代码:
for i 1 to n
for j m to 1//注意这里
dp[j] = max{dp[j - w[i]] + v[i]}
这里注意一个细节,你可以发现j这个循环的循环顺序被反过来了,为什么呢?
考虑到w[i]为正数,因此\(j - w[i] < j\),而我们的dp数组是需要用上一组的信息转移出下一组,因此我们需要保证,求dp[j]用到的转移状态是上一组的状态。
而求dp[j]时会用到比j小的状态,因此我们需要保证在dp[j]时,比j小的状态还没有被更新,因此我们用逆序枚举。
那么为什么我们必须用上一组的信息转移出下一组呢?
我们考虑如果我们用当前组转移当前组会发生什么。
可以发现,如果我们用当前组更新当前组,那就以为着用于更新的状态里已经考虑过当前物品了,这个时候你再加一次当前物品,相当于考虑了多次当前物品,不符合每个物品只能用一次的条件。
完全背包
有\(n\)种物品, 每种物品有一个价值\(vi\)和一个体积\(wi\),且数量为无限个,背包的最多容纳体积之和为\(m\)的物体。
现在让你求背包可以放入的最高价值之和。
从01背包的滚动数组优化的细节解释中,可以发现,其实我们顺序枚举,虽然不满足01背包的条件限制,但是却刚好满足完全背包的条件!
所以我们只需要将j顺序枚举,就是完全背包的做法。
伪代码:
for i 1 to n
for j 1 to m//注意这里
dp[j] = max{dp[j - w[i]] + v[i]}
多重背包
有\(n\)种物品, 每种物品有一个价值\(vi\)和一个体积\(wi\),且数量为\(ni\)个,背包的最多容纳体积之和为\(m\)的物体。
现在让你求背包可以放入的最高价值之和。
有2个思路,1个是把有\(ni\)个的第i种物品,拆分成\(ni\)个,当01背包做,1个是多一层循环,枚举第i个物品放进去了多少个。
复杂度都是\(\sum{ni} \cdot m\)的。
其实这2种思路本质上差不多,我们考虑二进制拆分进行分组来优化。
对于第i个物品,其实我们只需要对\(ni\)进行拆分,使得拆分出来的几个组,对于任意\(0<= x <= ni\),都有至少一种取舍方案使得第i种物品一共取走了x个.
要实现这个目标,二进制拆分是一个很好的选择。
我们先找到一个最大的k使得\(2^k<=ni\),然后令\(t = ni - 2^k\),易知\(t < 2^k\)
然后我们对\(2^k\)进行拆分,拆成\(2^0 + 2^1 + 2^3 ... + 2^{k - 1} + 1\),容易证明这k个数可以组合出0到\(2^k\)中任意一个数。
然后对于剩下的t我们单独作为一组,由于\(t < 2^k\),所以之前的k个数可以组合出0到t中任意一个数,而\(2^{k} + 1\)到\(ni\)中的任意一个数可以由t的这一组和之前的k组组合得到(你可以理解为取走所有组,然后我们需要删去一个0到t中的一个数来得到\(2^{k} + 1\)到\(ni\)中的某个数,而这个数肯定能被前k个数凑出)
------以上是最基础的三种背包问题-----下面会写一些扩展的背包问题(大概就是差不多的问题+一点点改动)-------有时间就更------
恰好装满
其实可以直接套01背包的做法,如果数据没有价值为0和负数的话(
然后看dp[m]有没有值就行。
如果有价值为0和负数……那就稍微改一下,令dp[i]表示考虑塞入体积之和为i,能否实现。
然后初始化一下,dp[0] = 0, 其他都是-inf,
最后看dp[m]如果还是-inf的话,说明不能恰好装满,如果不是-inf的话那dp[m]就是答案
多个限制
直接dp数组加维就行
求方案数
改一下dp方程
以01背包为例:\(dp[j] += sum{dp[j - w[i]] + v[i]}\),注意这里是+=,初始的dp[j]表示的是不放入当前物品,
完全背包思路类似