0-1背包

来源:互联网 发布:2016淘宝客微信教程 编辑:程序博客网 时间:2024/05/21 20:13

dcf

背包是一类典型的动归问题,有大神总结出来的背包九讲,0-1背包是其中的一种,也是最基本的一种。
之所以叫做 0-1背包是因为,这类问题中的每一件物体只有一件,放入背包与不放就构成了它的两种状态,0 代表不放,1 代表放入。


将这类问题总结一下就是:

Value (i,w) = Max( Value(i1,w),   Value(i1, ww[i] ) + v[i] )

解这道题的中点就在于,对于上面这个状态转移方程的理解。也就是把i个物品放入重量限制为W背包的中所取得的最大价值,取决于第i个物品的两种状态,即放与不放。

放,由于它会占有一定的重量,所以能取得的价值为v[i](它本身的价值)+Value(i-1, w-w[i])(前i-1件物品放入重量限制为w-w[i]的背包能取得的最大价值);

不放(放不下也属于),所能取得的价值就是Value(i-1, w)(即前i-1件物品放入重量限制为w的背包中)。

Value(i, w)即i件物品放入重量限制为w的背包中的最大价值就等于上面两种情况中的最大值。


这是一个动归理解的思想,我们在将它自底向上倒回去实现。
例如, 存在一个背包重量限制为10, 有5个物品重量价值分别为(5, 1)(2, 4)(3, 3)(4, 2)(5, 1),求最大价值。
此时我们用一个二位数组来记录对应的每一个value(i,w),从第一个物品开始放起,算出每一种情况下的最大价值,并逐步退出最终的解,即在重量限制10下,所能获得的最大价值。

(i,w) 0 1 2 3 4 5 6 7 8 9 10 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 1 1 1 1 1 2 0 0 0 0 2 2 2 2 2 3 3 3 0 0 0 3 3 3 3 5 5 5 5 4 0 0 4 4 4 7 7 7 7 9 9 5 0 5 5 9 9 9 12 12 12 12 14

类似于数字塔一样,我们可以从第一个物品开始装,并计算出各个情况下最大的价值,这样就可以依次计算二维数组中每一层的结果,最后输出[5,10]即在重量限制为10时放入第5个物品的最大价值。以这一题为例,结果就是v[5, 10] = 14。

例如:poj 3624 Charm Bracelet

Problem Description
Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course, she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a ‘desirability’ factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).

Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.

Input

  • Line 1: Two space-separated integers: N and M
  • Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di

Output

  • Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints

Sample Input

4 6
1 4
2 6
3 12
2 7

Sample Output

23

按照我们上面的思路我们就可以写出这样的代码

#include <iostream>#include <algorithm>using namespace std;int w[3500], d[3500];int f[13000][13000];int main(){    int n, wl;    cin>>n>>wl;    for(int i=1; i<=n; i++) cin>>w[i]>>d[i];    for(int i=1; i<=n; i++)    {        for(int j=1; j<=wl; j++)        {            if(j>=w[i]) f[i][j] = max(f[i-1][j], f[i-1][j-w[i]]+d[i]);            else f[i][j] = f[i-1][j];        }    }    cout<<f[n][wl]<<"\n";}

但事实上我们这样是过不了的,在其他oj上面可能有数据量比较小的题可以过,但是对于这道题来说,是会这样的Memory Limit Exceeded
其实也很好解决,因为我们仔细观察就可以发现,我们从底层往上推的时候,实际上只用到了当前的和上一列,此时我们就可以让数组变成这样 f[2][13000]。

for(int i=1; i<=n; i++)    {        for(int j=1; j<=wl; j++)        {            if(j-w[i]>=0) f[1][j] = max(f[0][j], f[0][j-w[i]]+d[i]);            else f[1][j] = f[0][j];        }        for(int j=0; j<=wl; j++) f[0][j] = f[1][j];    }

相当于滚动着去利用这个数组,这样做后,我们会发现,两列其实也是没必要的,可以利用一个一维数组,因为要用到前面的值,所以我们每一行的推导,变成从后往前,就不会影响到值的变化了。

#include <iostream>#include <algorithm>using namespace std;int w[3500], d[3500];int f[13000];int main(){    int n, wl;    cin>>n>>wl;    for(int i=1; i<=n; i++) cin>>w[i]>>d[i];    for(int i=1; i<=n; i++)    {        for(int j=wl; j>=1; j--)        {            if(j-w[i]>=0) f[j] = max(f[j], f[j-w[i]]+d[i]);            else f[j] = f[j];        }    }    cout<<f[wl]<<"\n";}
0 0