Loading

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\n",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\n",dp[n][m]);
     }
     return 0;
}
/*

*/ 

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

posted @ 2014-03-08 20:51  北岛知寒  阅读(507)  评论(0编辑  收藏  举报