背包问题笔记

参考: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的顺序循环。这就是这个简单的程序为何成立的道理。

原文地址:https://www.cnblogs.com/smartjune/p/5452578.html