背包问题笔记
参考:http://www.cnblogs.com/jbelial/articles/2116074.html
一、01背包问题
题目
有N件
物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大.(hihoCoder)
基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即dp[i][v]表示前i件物品放入一个容量为v的背包(允许不放满)可以获得的最大价值
。则其状态转移方程便是:
dp[i][v] = max{ dp[i-1][v], dp[i-1][v-c[i]] + w[i] }`,
解释: max{ 不放第i件物品,放第i件物品 }
即:max{ 前i-1件物品已经占据了v的容量,前i-1件物品占据了v-c[i]的容量 }
优化空间复杂度
以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V)。
首先最直接是开数组 dp[N][M]
然后观察,明显可以优化到 dp[2][M]
再考虑,如果只用 dp[M],能不能保证第i次循环结束后dp[v]中表示的就是我们定义的状态dp[i][v]呢? dp[i][v]是由dp[i-1][v]和dp[i-1] [v-c[i]]两个子问题递推而来,能否保证在推dp[i][v]时(也即在第i次主循环中推dp[v]时)能够得到dp[i-1][v]和dp[i-1][v -c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推dp[v],这样才能保证推dp[v]时dp[v-c[i]]保存的是状态dp[i -1][v-c[i]]的值。伪代码如下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
二、完全背包问题
题目
有N种
物品和一个容量为V的背包,每种物品都有无限件可用
。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。hihoCoder
思路:转化为01背包问题求解
根据01背包的做法,对于一个问题 dp(i, x),考虑最后一步——即第i件物品选择多少件,不妨就假设选择k件吧,那么k的取值范围肯定是在0~(x / need(i))这个范围内。所以一种可能的方案是 dp(i - 1, x - need(i) * k) + value(i) * k
,而最优方案是:
dp[i][x] = max{ dp[i-1][x - need[i]k] + value[i]k }, 其中 0 <= k <= x/need[i]
但时间复杂度超过 O(N*V)。
优化状态转移方程
考虑最后一步要不要再背一件第i种物品,用 dp[i][v]表示完成前i种物品的决策时可以获得的最大价值
,有状态转移方程:
dp[i][v] = max{ dp[i-1][v], dp[i][v-c[i]] + w[i] }
解释:dp[i-1][v]表示考虑完最后一步后背包里不含有第i种物品,dp[i][v-c[i]] + w[i]表示考虑完最后一步后背包里含有了若干件第i种物品。
优化空间复杂度
与01背包类似。伪代码:
for i=1..N
for v=0..V
f[v]=max{f[v],f[v-c[i]]+w[i]};
这个伪代码与P01的伪代码只有v的循环次序不同而已。为什么这样一改就可行呢?首先想想为什么P01中要按照v=V..0的逆序来循环。这是因为要保证第i次循环中的状态f[i][v]是由状态f[i-1][v-c[i]]递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果f[i-1][v-c[i]]。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果f[i][v-c[i]],所以就可以并且必须采用v= 0..V的顺序循环。这就是这个简单的程序为何成立的道理。