一、01背包
以前不明白为什么这里需要用倒序来推导,现在看明白了。
因为在每一次主循环开始时,dp数组存贮着的就是i-1的状态。
而如果是顺序的,那么假如当前更新到了dp[j],此时的dp[j-c[i]]是已经在本轮中更新过了的,相当于是dp[i][j-c[i]]而不是dp[i-1][j-c[i]]。
这张截图也说得很清楚了。
(2)时间小优化——
第二层循环只进行到cost
(3)初始化的细节——
由两种不同的提问方式来区分
如果是“要求恰好装满”,那么则要使dp[0]=0,而dp[1...W]=-inf.
如果是“不要求恰好装满”,那么dp{0...W]=0.
这是我自己的解释:
因为我们只用一个数组,那么dp初始化的值在将来什么时候用都不确定。
如果是“恰好装满”,那么每个dp的含义都应该是是代表已经装了j重量的物品后可以获得的最大价值。
那么当前任一个dp[j](j!=0)值都不可能是零,因为它里面必须装有东西!!!
除非存在价值为零的商品,且所有其他商品的价值都比零小,而这种题目一般不会出现吧。
如果是“非恰好装满”,那么每个dp的含义应该是代表容量大小为j的背包可以获得的最大价值。
而这个时候,dp[j]为零就是合法的。
(4)一个常数优化
前面的伪代码中有for v=V..1,可以将这个循环的下限进行改进。
由于只需要最后f[V]的值,倒推前一个物品,其实只要知道f[V-w[n]]即可。以此类推,对以
第j个背包,其实只需要知道到f[V-(后n-j个物品的重量总和)]即可,即代码中的
二、完全背包:
(1)最最基础的思路:
(2)但是上面这个时间复杂度太高,可以加一个优化,
(3)接下来是一种更为简单有效的办法。
我们惯常是以+1来枚举的。但是对于某当前的剩余容量V,和当前的物品,
我们最多可以拿V/c[i] 个,而这个数我们可以用二进制来表示,也就是说我们
只需要确定重量为1c[i],2c[i] ,3c[i] ,4c[i] ...的商品拿一件还是不拿就可以了
这样我们就回归到了01背包的问题之中。
那么复杂度就是O{N·V·logV/c[i]}
(4)但是上面的始终是三层循环,实际上我们有O{N·V}的算法。
我自己的理解是,对于每一个状态dp[j](不管是当前i还是上一个循环的i-1)我们都可以选择“维持当前状态不拿商品”,也可以“拿商品”。在下一个状态dp[j+c[i]]也是同样操作。这样一来就是实现了从一种物品一个不拿到一个物品拿多个的各种情况的最优化选择。
三、多重背包:
记住!只有形如 dp[i]=max/min (f[k]) + g[i] (k<i && g[i]是与k无关的变量)才能用到单调队列进行优化。
优化的对象就是f[k]。