zoukankan      html  css  js  c++  java
  • 0-1背包详解

    •  写在前面

    背包问题是动态规划里面很重要的一部分,彻底理解各种背包问题,对动态规划的后续学习有很大的帮助.

    更全的背包问题,可参看《背包九讲》

    一.什么是“0-1背包”?

    有这样一个问题:

      在你面前放着n颗宝石,每颗宝石重量为wi,价值为vi;你有一个最多可以放m重量的背包。现在你想在不超重的情况下,是你带走的宝石价值最大,问最大价值是多少?

    由于每种物品只有一件,对于第i件物品只有两种可能:拿或不拿。故称为“0-1”背包.

    如果每种物品有多个,那就是“多重背包”.

    如果每种物品的数量是无限的,那就是“完全背包”.

    二.如何做?

    很显然,贪心算法无法保证最优解,只能用动态规划来做.

    我们从第一件物品开始,逐一往后决策.

    设:

      dp[i][j]表示:前i件物品,当背包限制容量为j时,能够取到的最大价值总和.

    例如:有5件物品,背包容量为10.其中每件物品的重量和价值如下:

    物品 重量 价值
    1 2 6
    2 2 3
    3 6 5
    4 5 4
    5 4 6 

    初始状态dp数组:

    物品 wi vi 0 1 2 3 4 5 6 7 8 9 10
    0 - - 0 0 0 0 0 0 0 0 0 0 0
    1 2 6                      
    2 2 3                      
    3 6 5                      
    4 5 4                      
    5 4 6                      

    在填dp[1][0]时,显然容量小于2以前都为0.

    物品 wi vi 0 1 2 3 4 5 6 7 8 9 10
    0 - - 0 0 0 0 0 0 0 0 0 0 0
    1 2 6 0 0                  
    2 2 3                      
    3 6 5                      
    4 5 4                      
    5 4 6                      

    到了dp[1][2],我们发现容量2可以装下第一件物品,所以dp[1][2]=6.

    而且后面的都是为6,因为现在只决策到第1件物品,不考虑后面的物品.

    物品 wi vi 0 1 2 3 4 5 6 7 8 9 10
    0 - - 0 0 0 0 0 0 0 0 0 0 0
    1 2 6 0 0 6 6 6 6 6 6 6 6 6
    2 2 3                      
    3 6 5                      
    4 5 4                      
    5 4 6                      

    同理,一直填到dp[2][2],这个时候有两个选择,要么拿2,要么不拿2。拿2的最大价值为3 ,不拿2的最大价值为6,所以我们选择不拿2.

    物品 wi vi 0 1 2 3 4 5 6 7 8 9 10
    0 - - 0 0 0 0 0 0 0 0 0 0 0
    1 2 6 0 0 6 6 6 6 6 6 6 6 6
    2 2 3 0 0 6 6              
    3 6 5                      
    4 5 4                      
    5 4 6                      

    关键!关键到了dp[2][4].

    两种情况:

      1.拿2

        背包装了2号物品后,剩余容量为4-2=2。现在问题转化为:前i-1件物品,背包最大容量为2时,能够获得的最大价值,对应的也就是dp[i-1][j-w[i]]。

      2.不拿2

         dp[2][4]=dp[i-1][j]=dp[1][4]=6.

    通过计算,我们发现拿2可以获得更大的价值,也就是dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]).

    剩下的以此类推,最大的价值就是dp[n][m].

    物品 wi vi 0 1 2 3 4 5 6 7 8 9 10
    0 - - 0 0 0 0 0 0 0 0 0 0 0
    1 2 6 0 0 6 6 6 6 6 6 6 6 6
    2 2 3 0 0 6 6 9 9 9 9 9 9 9
    3 6 5 0 0 6 6 9 9 9 9 11 11 14
    4 5 4 0 0 6 6 9 9 9 10 11 13 14
    5 4 6 0 0 6 6 9 9 12 12 15 15 15

    还有一个问题:如何知道最终选择的是哪些物品?

    很简单,我们从dp[n][m]开始往上走:

      1.如果dp[i][j]>dp[i-1][j],说明物品i是选择了的物品,记录之。此时问题转化成了i-1件物品,背包最大容量为j-w[i]时的情况,即:跳转到dp[i-1][j-w[i]]继续判断;

      2.如果dp[i][j]==dp[i-1][j],说明物品j未选择。继续考虑dp[i-1][j].

    重复上述判断,直到走到i=0为止,即可得到所选择的物品。

     

     

     

     

     

     

     

     

     

    所以选择的物品为:1,2,5. 

    三.空间优化

    前面我们都是用一个大小为N*W的dp数组来存储状态,很容易爆内存,怎样优化内存呢?

    逆序!

    for(i=1 to N)
       for(j=W to 0)
           dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

    看懂了没?原来的方法:在计算dp[i][j]时,只用到了上一行中第j列以及第j列之前的dp值.

    将内循环逆序,保证了正确性,空间瞬间从N*W变为了W,再也不用担心爆内存了.

    当然,如果需要知道选择了哪些物品的话,还是需要第一种方法. 

    代码1:

    /*
    * this code is made by crazyacking
    * Verdict: Accepted
    * Submission Date: 2013-10-28-17.43
    * Time: 0MS
    * Memory: 137KB
    */
    #include <queue>
    #include <cstdio>
    #include <set>
    #include <string>
    #include <stack>
    #include <cmath>
    #include <climits>
    #include <map>
    #include <cstdlib>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #define max(a,b) (a>b?a:b)
    using namespace std;
    typedef long long(LL);
    typedef unsigned long long(ULL);
    const double eps(1e-8);
    
    const int N=110;
    const int M=10010;
    int w[N],v[N],dp[M];
    int n,m;
    int main()
    {
         ios_base::sync_with_stdio(false);
         cin.tie(0);
         while(cin>>n>>m)
         {
               for(int i=1;i<=n;++i)
                     cin>>w[i]>>v[i];
               for(int i=0;i<=m;++i)
                     dp[i]=0;
               for(int i=1;i<=n;++i)
               {
                     for(int j=m;j>=0;--j)
                     {
                           if(j-w[i]<0)
                                 continue;
                           dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
                     }
               }
               printf("%d
    ",dp[m]);
         }
         return 0;
    }
    /*
    
    */

    代码2:

    /*
    * this code is made by crazyacking
    * Verdict: Accepted
    * Submission Date: 2013-10-26-12.36
    * Time: 0MS
    * Memory: 137KB
    */
    #include <queue>
    #include <cstdio>
    #include <set>
    #include <string>
    #include <stack>
    #include <cmath>
    #include <climits>
    #include <map>
    #include <cstdlib>
    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <cstring>
    #define max(a,b) (a>b?a:b)
    using namespace std;
    typedef long long(LL);
    typedef unsigned long long(ULL);
    const double eps(1e-8);
    
    const int N=110;
    const int W=10010;
    int n,m;
    int v[N],w[N];
    int dp[N][W];
    
    void print_dp()
    {
         puts("-----------------------------------------------------------------");
         for(int i=0; i<=n; ++i)
         {
               for(int j=0; j<=m; ++j)
               {
                     printf("%3d ",dp[i][j]);
               }
               puts("");
         }
         puts("-----------------------------------------------------------------");
    
    }
    
    void print_select()
    {
         vector<int> sel;
         int i=n,j=m;
         for(; i>=1; --i)
         {
               if(dp[i][j]>dp[i-1][j]) // selected
               {
                     sel.push_back(i);
                     j-=w[i];
               }
         }
         reverse(sel.begin(),sel.end());
         puts("-----------------------------------------------------------------");
    
         for(int i=0; i<sel.size(); ++i)
         {
               printf("%d ",sel[i]);
         }
         puts("");
         puts("-----------------------------------------------------------------");
    
    }
    
    int main()
    {
         ios_base::sync_with_stdio(false);
         cin.tie(0);
         while(~scanf("%d %d",&n,&m))
         {
               for(int i=1; i<=n; ++i)
               {
                     scanf("%d %d",&w[i],&v[i]);
               }
               for(int i=0; i<=m; ++i)
                     dp[0][i]=0;
               for(int i=1; i<=n; ++i)
               {
                     for(int j=0; j<=m; ++j)
                     {
                           if(j-w[i]>=0)
                                 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
                           else dp[i][j]=max(dp[i-1][j],0);
                     }
               }
    
               print_dp();
               print_select();
               printf("%d
    ",dp[n][m]);
         }
         return 0;
    }
    /*
    
    */ 

    转载请注明:http://www.cnblogs.com/crazyacking/p/3588565.html

  • 相关阅读:
    try,catch,finally的简单问题
    设置类可序列化,写入VIewState
    jQuery实现购物车物品数量的加减 (针对GirdView的类似事件)
    js获取Gridview中的控件id
    asmx ASp.net AJAX使用 ScriptManager
    js返回上一页并刷新,JS实现关闭当前子窗口,刷新父窗口
    asp.net(c#)网页跳转七种方法小结
    在触发器中回滚和提交
    redis 缓存对象、列表
    spring cloud 停止服务
  • 原文地址:https://www.cnblogs.com/crazyacking/p/3588565.html
Copyright © 2011-2022 走看看