经典背包问题 01背包+完全背包+多重背包

来源:互联网 发布:1元洗车软件 编辑:程序博客网 时间:2024/04/30 08:16

转载来源 http://blog.csdn.net/lyhvoyage/article/details/8545852

01 背包

有n 种不同的物品,每个物品有两个属性,size 体积,value 价值,现在给一个容量为 w 的背包,问最多可带走多少价值的物品。  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int f[w+1];   //f[x] 表示背包容量为x 时的最大价值  
  2. for (int i=0; i<n; i++)  
  3.     for (int j=w; j>=size[i]; j--)  
  4.         f[j] = max(f[j], f[j-size[i]]+value[i]);  



完全背包 

如果物品不计件数,就是每个物品不只一件的话,稍微改下即可  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for (int i=0; i<n; i++)  
  2.     for (int j=size[i]; j<=w; j++)  
  3.         f[j] = max(f[j], f[j-size[i]]+value[i]);  

  
        f[w] 即为所求  
        初始化分两种情况:
        1、如果背包要求正好装满则初始化 f[0] = 0, f[1~w] = -INF;  

        2、如果不需要正好装满 f[0~v] = 0;  

        举例:

01背包

V=10,N=3,c[]={3,4,5}, w={4,5,6}

(1)背包不一定装满

      计算顺序是:从右往左,自上而下:因为每个物品只能放一次,前面的体积小的会影响体积大的

(2)背包刚好装满    

      计算顺序是:从右往左,自上而下。注意初始值,其中-inf表示负无穷

完全背包

V=10,N=3,c[]={3,4,5}, w={4,5,6}

(1)背包不一定装满

计算顺序是:从左往右,自上而下:  每个物品可以放多次,前面的会影响后面的

(2)背包刚好装满

计算顺序是:从左往右,自上而下。注意初始值,其中-inf表示负无穷

多重背包:  
         多重背包问题要求很简单,就是每件物品给出确定的件数,求可得到的最大价值  
         多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用二进制分解成若干个件数的集合,这里面数字可以组合成任意小于等于C的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可以用数字的二进制形式来解释  
       比如:7的二进制 7 = 111 它可以分解成 001 010 100 这三个数可以组合成任意小于等于7 的数,而且每种组合都会得到不同的数  
       15 = 1111 可分解成 0001  0010  0100  1000 四个数字  
        如果13 = 1101 则分解为 0001 0010 0100 0110 前三个数字可以组合成  7以内任意一个数,即1、2、4可以组合为1——7内所有的数,加上 0110 = 6 可以组合成任意一个大于6 小于等于13的数,比如12,可以让前面贡献6且后面也贡献6就行了。虽然有重复但总是能把 13 以内所有的数都考虑到了,基于这种思想去把多件物品转换为,多种一件物品,就可用01 背包求解了。  
       看代码:  
      
[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int n;  //输入有多少种物品  
  2. int c;  //每种物品有多少件  
  3. int v;  //每种物品的价值  
  4. int s;  //每种物品的尺寸  
  5. int count = 0; //分解后可得到多少种物品  
  6. int value[MAX]; //用来保存分解后的物品价值  
  7. int size[MAX];  //用来保存分解后物品体积  
  8.   
  9. scanf("%d", &n);    //先输入有多少种物品,接下来对每种物品进行分解  
  10.   
  11. while (n--)     //接下来输入n中这个物品  
  12. {  
  13.     scanf("%d%d%d", &c, &s, &v);  //输入每种物品的数目和价值  
  14.     for (int k=1; k<=c; k<<=1)   //<<右移 相当于乘二  
  15.     {  
  16.         value[count] = k*v;  
  17.         size[count++] = k*s;  
  18.         c -= k;  
  19.     }  
  20.     if (c > 0)  
  21.     {  
  22.         value[count] = c*v;  
  23.         size[count++] = c*s;  
  24.     }  
  25. }  


定理:一个正整数n可以被分解成1,2,4,…,2^(k-1),n-2^k+1(k是满足n-2^k+1>0的最大整数)的形式,且1~n之内的所有整数均可以唯一表示成1,2,4,…,2^(k-1),n-2^k+1中某几个数的和的形式。

证明如下:

(1) 数列1,2,4,…,2^(k-1),n-2^k+1中所有元素的和为n,所以若干元素的和的范围为:[1, n];

(2)如果正整数t<= 2^k – 1,则t一定能用1,2,4,…,2^(k-1)中某几个数的和表示,这个很容易证明:我们把t的二进制表示写出来,很明显,t可以表示成n=a0*2^0+a1*2^1+…+ak*2^(k-1),其中ak=0或者1,表示t的第ak位二进制数为0或者1.

(3)如果t>=2^k,设s=n-2^k+1,则t-s<=2^k-1,因而t-s可以表示成1,2,4,…,2^(k-1)中某几个数的和的形式,进而t可以表示成1,2,4,…,2^(k-1),s中某几个数的和(加数中一定含有s)的形式。

(证毕!)


      

        现在用count 代替 n 就和01 背包问题完全一样了  

杭电2191题解:此为多重背包用01和完全背包:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<stdio.h>  
  2. #include<string.h>  
  3. int dp[102];  
  4. int p[102],h[102],c[102];  
  5. int n,m;  
  6. void comback(int v,int w)//经费,重量。完全背包;  
  7. {  
  8.     for(int i=v; i<=n; i++)  
  9.         if(dp[i]<dp[i-v]+w)  
  10.             dp[i]=dp[i-v]+w;  
  11. }  
  12. void oneback(int v,int w)//经费,重量;01背包;  
  13. {  
  14.     for(int i=n; i>=v; i--)  
  15.         if(dp[i]<dp[i-v]+w)  
  16.             dp[i]=dp[i-v]+w;  
  17. }  
  18. int main()  
  19. {  
  20.     int ncase,i,j,k;  
  21.     scanf("%d",&ncase);  
  22.     while(ncase--)  
  23.     {  
  24.         memset(dp,0,sizeof(dp));  
  25.         scanf("%d%d",&n,&m);//经费,种类;  
  26.         for(i=1; i<=m; i++)  
  27.         {  
  28.             scanf("%d%d%d",&p[i],&h[i],&c[i]);//价值,重量,数量;  
  29.             if(p[i]*c[i]>=n) comback(p[i],h[i]);  
  30.             else  
  31.             {  
  32.                 for(j=1; j<c[i]; j<<1)  
  33.                 {  
  34.                     oneback(j*p[i],j*h[i]);  
  35.                     c[i]=c[i]-j;  
  36.                 }  
  37.                 oneback(p[i]*c[i],h[i]*c[i]);  
  38.             }  
  39.         }  
  40.         printf("%d\n",dp[n]);  
  41.     }  
  42.     return 0;  
  43. }  


完全背包另外一种写法

int max(int a,int b){if(a>b)return a;return b;}void onepack(int v,int w,int lim){int i;for(i=lim;i>=v;i--){dp[i]=max(dp[i],dp[i-v]+w);}}void com(int v,int w,int lim){int i;for(i=v;i<=lim;i++){dp[i]=max(dp[i],dp[i-v]+w);}}void multi(int v,int w,int lim,int num){int i,j;if(num*v>=lim)com(v,w,lim);else{for(j=1;j<num;j<<=1){onepack(j*v,j*w,lim);num-=j;}onepack(num*v,num*w,lim);}}



只是用01背包,用二进制优化:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <iostream>  
  2. using namespace std;  
  3. int main()  
  4. {  
  5.     int nCase,Limit,nKind,i,j,k,  v[111],w[111],c[111],dp[111];  
  6.     //v[]存价值,w[]存尺寸,c[]存件数  
  7.     //在本题中,价值是米的重量,尺寸是米的价格  
  8.     int count,Value[1111],size[1111];  
  9.     //count存储分解完后的物品总数  
  10.     //Value存储分解完后每件物品的价值  
  11.     //size存储分解完后每件物品的尺寸  
  12.     cin>>nCase;  
  13.     while(nCase--)  
  14.     {  
  15.         count=0;  
  16.         cin>>Limit>>nKind;  
  17.         for(i=0; i<nKind; i++)  
  18.         {  
  19.             cin>>w[i]>>v[i]>>c[i];  
  20.             //对该种类的c[i]件物品进行二进制分解  
  21.             for(j=1; j<=c[i]; j<<=1)  
  22.             {  
  23.                 //<<右移1位,相当于乘2  
  24.                 Value[count]=j*v[i];  
  25.                 size[count++]=j*w[i];  
  26.                 c[i]-=j;  
  27.             }  
  28.             if(c[i]>0)  
  29.             {  
  30.                 Value[count]=c[i]*v[i];  
  31.                 size[count++]=c[i]*w[i];  
  32.             }  
  33.         }  
  34.         //经过上面对每一种物品的分解,  
  35.         //现在Value[]存的就是分解后的物品价值  
  36.         //size[]存的就是分解后的物品尺寸  
  37.         //count就相当于原来的n  
  38.         //下面就直接用01背包算法来解  
  39.         memset(dp,0,sizeof(dp));  
  40.         for(i=0; i<count; i++)  
  41.             for(j=Limit; j>=size[i]; j--)  
  42.                 if(dp[j]<dp[j-size[i]]+Value[i])  
  43.                     dp[j]=dp[j-size[i]]+Value[i];  
  44.   
  45.         cout<<dp[Limit]<<endl;  
  46.     }  
  47.     return 0;  
  48. }  


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 冲话费冲错了怎么办 微信支付未到账怎么办 速卖通修补配件到国外怎么办 速卖通流量低怎么办 速卖通被判定重复铺货怎么办 拼多多商品降权怎么办 运满满有了差评怎么办 房贷款还清后该怎么办 身份证被偷了怎么办啊 苹果手机wifi速度慢怎么办 电脑桌面上的图标不见了怎么办 夏天手机没地方放怎么办 上班手机没地方放怎么办 京东退款未到账怎么办 京东退款失败后怎么办 在京东申请退款怎么办 微信退款没收到钱怎么办 在拼多多不发货怎么办 扫二维码群发微信骗局怎么办 电脑高清晰音频管理器打不开怎么办 吃鸡耳机有杂音怎么办 分期付款车被朋友卖了怎么办 网上购物付款显示繁忙怎么办 同行招牌高于我的招牌怎么办 拼多多刷手退款怎么办 网银卡在手机上卸载了怎么办 企业网银密码忘了怎么办 网银钱转错了怎么办 民生百货购物卡过期了怎么办 新办卡注册过支付宝账号怎么办 床上用品专卖店没人进店怎么办 红蚂蚁咬了红肿痒怎么办 碎纸机轮不转了怎么办 轮滑鞋刀架螺丝圆了了怎么办 万朋商城2018年怎么办 超市盘点少了烟怎么办 歌华有线电视费用欠费好久怎么办 租房到期房东不退押金怎么办 个税申报错税局查出来怎么办 苹果手表抬腕唤醒失灵怎么办 在京东充电费充错了怎么办