01背包 完全背包

01背包和完全背包

刚接触背包问题 看了很多博客 才算弄懂这01背包和完全背包  所以赶紧发个博客记录下来  虽然我标注的是原创 但是肯定会引用到别人的东西  如果是引用的 我会注明出处 尊重原创嘛吐舌头



先说下 01背包 

01背包的问题描述是这样的:


N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。



01背包的特点就是:每种物品都只有一件,选择放还是不放。

状态转移方程是:

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

对于这方方程其实并不难理解,方程之中,现在需要放置的是第i件物品,这件物品的体积是c[i],价值是w[i],因此f[i-1][v]代表的就是不将这件物品放入背包,而f[i-1][v-c[i]]+w[i]则是代表将第i件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。

用代码实现:

[cpp]  view plain  copy
  1. for(i = 1; i<=n; i++)  
  2. {  
  3.     for(j = v; j>=c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了  
  4.     {  
  5.         f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);  
  6.     }  
  7. }  

HDU2546:饭卡


http://acm.hdu.edu.cn/showproblem.php?pid=2546


很经典的一道01背包题,要注意的是这里只要剩余的钱不低于5元,就可以购买任何一件物品,所以5在这道题中是很特许的,再使用01背包之前,我们首先要在现在所拥有的余额中保留5元,用这五元去购买最贵的物品,而剩下的钱就是背包的总容量,可以随意使用,因此可得代码

[cpp]  view plain  copy
  1. #include <stdio.h>  
  2. #include <algorithm>  
  3. using namespace std;  
  4.   
  5. int cmp(int a,int b)  
  6. {  
  7.     return a<b;  
  8. }  
  9.   
  10. int main()  
  11. {  
  12.     int n;  
  13.     while(~scanf("%d",&n),n)  
  14.     {  
  15.         int i,price[2013]= {0},dp[2013] = {0};  
  16.         for(i = 1; i<=n; i++)  
  17.             scanf("%d",&price[i]);  
  18.         sort(price+1,price+1+n,cmp);  
  19.         int MAX=price[n];  
  20.         int j,m;  
  21.         scanf("%d",&m);  
  22.         if(m<5)//低于5元不能购买  
  23.         {  
  24.             printf("%d\n",m);  
  25.             continue;  
  26.         }  
  27.         m-=5;//取出5元用于购买最贵的物品  
  28.         for(i = 1; i<n; i++)//01背包  
  29.         {  
  30.             for(j = m;j>=price[i];j--)  
  31.             {  
  32.                 dp[j] = max(dp[j],dp[j-price[i]]+price[i]);  
  33.             }  
  34.         }  
  35.         printf("%d\n",m+5-dp[m]-MAX);  
  36.     }  
  37.   
  38.     return 0;  
  39. }  


(出自: http://blog.csdn.net/libin56842/article/details/9338841



完全背包:


完全背包是在N 物品中选取若干件(同一种物品可多次选取)放在空间为V的背包里,每 物品的体积为C[i] ,与之相对应的价值为W[i]   .求解怎么装物品可使背包里物品总价值最大。


阶段是:在前N件物品中,选取若干件物品放入背包中;  

 

状态是:在前N件物品中,选取若干件物品放入所剩空间为W的背包中的所能获得的最大价值;


决策是:第N件物品放或者不放;   


状态转移方程是:



f[i][v]=max(f[i-1][v-k*c[i]]+k*w[i]0<=k*c[i]<=v


换成更易理解的方式 则方程转换为:


for(i=0;i<n;i++)

{
     for(v=0;v<V;v++)

     f[i][v]=max(f[i-1][v] , f[i-1][v-c[i]]+w[i])

}

既然 01 背包问题是最基本的背包问题,那么我们可以考虑把完全背包问题转化为 01 背包问题来解。最简单的想法是,考虑到第 i 种物品最多选 V/c[i] 件,于是可以把第 i 种物品转化为 V/c[i] 件费用及价值均不变的物品,然后求解这个 01 背包问题。但是这样完全没有改进基本思路的时间复杂度,不过教给我们一种思想: 将一种物品拆成多件物品。

  更高效的转化方法是: 因为同种物品可以多次选取,那么第i种物品最多可以选取V/C[i]件价值不变的物品,然后就转化为01背包问题。整个过程的时间复杂度并未减少。如果把第i种物品拆成体积为C[i]×2k价值W[i]×2k的物品,其中满足C[i]×2k≤V。那么在求状态F[i][j]时复杂度就变为O(log2(V/C[i]))。整个时间复杂度就变为O(NVlog2(V/C[i]))



但是还有更优的算法:

将f[i][v]数组转化成一维数组:


               for(i=0;i<m;i++)
{
for(j=c[i];j<=n;j++)
f[j]=max(f[j],f[j-c[i]]+w[i]);
}

想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。
现在关键的是考虑:为何完全背包可以这么写?
在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。
那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何?
因为每种背包都是 无限 的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态


然后我们看一道题:


F. 完全背包

Time Limit: 3000ms
Memory Limit: 128000KB
64-bit integer IO format:      Java class name

直接说题意,完全背包定义有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。本题要求是背包恰好装满背包时,求出最大价值总和是多少。如果不能恰好装满背包,输出NO

Input

第一行: N 表示有多少组测试数据(N<7)。 
接下来每组测试数据的第一行有两个整数M,V。 M表示物品种类的数目,V表示背包的总容量。(0<M<=2000,0<V<=50000)
接下来的M行每行有两个整数c,w分别表示每种物品的重量和价值(0<c<100000,0<w<100000)

Output

对应每组测试数据输出结果(如果能恰好装满背包,输出装满背包时背包内物品的最大价值总和。 如果不能恰好装满背包,输出NO)

Sample Input

2
1 5
2 2
2 5
2 2
5 1

Sample Output

NO
1

(出自:http://blog.csdn.net/red_flame/article/details/8152436
这里唯一不同的是背包如果不能完全装满,则输出NO,这里需要一个技巧,就是初始化时f[0],其余的均为-max,只有这样最大值为正时,只能通过f[0]在相加其他价值得到,如 背包体积为4时, 一种物品体积2,价值2; 则             f[0]=0;         f[1]=-max;         f[2]=max(f[2],f[0]+w[i])=2; 注意若背包不需要全部装满时,f[3]本该为2的,但此时        f[3]=max(f[3],f[1]+2)=max(f[3],2-max)=2-max; 负无穷


#include<iostream>
#include<stdio.h>
#include<string>
#include<cstring>
using namespace std;
int f[50010],c[2010],w[2010];
int main()
{
 int test,m,v,i,j;
 scanf("%d",&test);
 while(test--)
 {
  memset(f,-10000000,sizeof(f));                 //用来判断背包是否装满
  f[0]=0;
  scanf("%d%d",&m,&v);
  for(i=1;i<=m;++i)
  scanf("%d%d",&c[i],&w[i]);
  for(i=1;i<=m;++i)
  for(j=0;j<=v;++j)             //注意此循环与01背包的用一维数组表示的状态方程的区别,一个循环逆序,一个顺序
  if(j>=c[i])
  f[j]=f[j]>(f[j-c[i]]+w[i])?f[j]:f[j-c[i]]+w[i];               //完全背包的状态方程,可画图加深理解
  if(f[v]<0)                   //背包为装满
  printf("NO\n");
  else
  printf("%d\n",f[v]);
 }
}

     
     
    • 0
      点赞
    • 0
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值