0-1背包总结

                       今天上午系统性地对“0-1背包”问题做了针对性的题目,已经大概明白基本思路了,所以做个小总结。

                              本篇总结大部分内容引用自博客:http://blog.csdn.net/libin56842/article/details/9338841


       1. 0-1背包题目背景:
        有 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件放入背包之后的总价值,比较两者的价值,得出最大的价值存入现在的背包之中。

理解了这个方程后,将方程代入实际题目的应用之中,可得

<pre name="code" class="cpp">for(i = 1; i<=n; i++)
{
    for(j = v; j>=c[i]; j--)//在这里,背包放入物品后,容量不断的减少,直到再也放不进了
    {
        f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]);
    }
}


 
 


2. 例题分析:

(1)HDU-2546:饭卡

题目源地址:http://acm.hdu.edu.cn/showproblem.php?pid=2546

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

//HOJ--2546:饭卡           0-1背包 
#include<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
int cmp(int a,int b)
{
    return a<b;
}
int main()
{
    int n,m;
    int price[1010],dp[1010];
    int i,j;
    
    while(cin>>n && n)
    {
       for(i=1;i<=n;i++)
          cin>>price[i];
       cin>>m;
       
       memset(dp,0,sizeof(dp));
       sort(price+1,price+1+n,cmp);
       
       int MAX=price[n];
       
       if(m<5)//低于5元不能购买  
       {
          cout<<m<<endl;
          continue;
       }
       
       m=m-5;//取出5元用于购买最贵的物品  
       for(i=1;i<n;i++)//01背包
       {
          for(j=m;j>=price[i];j--)
             dp[j]=max(dp[j],dp[j-price[i]]+price[i]);
       }
       
       int ans=m+5-dp[m]-MAX;
       cout<<ans<<endl;   
    }
    return 0;
}

              (2)HDU-1171:Big Event in HDU

题目源地址:http://acm.hdu.edu.cn/showproblem.php?pid=1171

       这道题咋看有点复杂,其实也只是换了一种思维,因为题目要求要尽量平均分配,所以我们可以先将总价值sum求出,然后得出其分配的平均值为sum/2,要注意这个答案可能为小数,但是又因为sum是整数,所以最后得出的sum/2是要小于等于实际的值。将这个结果进行01,背包,可以得出其中一个宿舍所得的最大价值,而另一个宿舍的最大价值也可以相应的得到,而前者必定小于等于后者。

<pre name="code" class="cpp">//HOJ--1171:Big Event in HDU
#include<iostream>
#include<algorithm>
#include<memory.h>
using namespace std;
int val[5005];
int dp[255555];
int main()
{
    int n,sum;
    int i,j,a,b,k;
    
    while(cin>>n && n>0)
    {
       memset(val,0,sizeof(val));
       memset(dp,0,sizeof(dp));
       
       k=0;
       sum=0;
       for(i=0;i<n;i++)
       {
          cin>>a>>b;
          while(b--)
          {
             val[k++]=a;//将价值存入数组
             sum=sum+a; 
          }
       }
       
       for(i=0;i<k;i++)
       {
          for(j=sum/2;j>=val[i];j--)
             dp[j]=max(dp[j],dp[j-val[i]]+val[i]);
       }
       cout<<sum-dp[sum/2]<<" "<<dp[sum/2]<<endl;
    }
    return 0;
} 
 
 


(3)HDU-2602:Bone Collector

题目源地址:http://acm.hdu.edu.cn/showproblem.php?pid=2602

最经典最简单的0-1背包问题。

//HOJ--2602:Bone Collector
#include<iostream>
#include<memory.h>
#include<algorithm>
using namespace std;
int main()
{
    int caseNum,N,V;
    int value[1010],volume[1010],dp[10000];
    int i,j;
    
    cin>>caseNum;
    while(caseNum--)
    {
       cin>>N>>V;
       
       memset(value,0,sizeof(value));
       memset(volume,0,sizeof(volume));
       memset(dp,0,sizeof(dp));
       
       for(i=1;i<=N;i++)
          cin>>value[i];
        
       for(i=1;i<=N;i++)
          cin>>volume[i];
          
       for(i=1;i<=N;i++)
       {
          for(j=V;j>=volume[i];j--)
             dp[j]=max(dp[j],dp[j-volume[i]]+value[i]);
       }
       cout<<dp[V]<<endl;    
    }
    return 0;
}<span style="color:#ff0000;">
</span>

         (4)HDU-2955:Robberies

题目源地址:http://acm.hdu.edu.cn/showproblem.php?pid=2955

分析:
1、抢n个银行,不被抓,是每次都不被抓,用乘法原理处理每次不被抓的概率。
2、转化为0 1 背包:将所有银行的总钱数当做背包的容积v,将不被抓的概率当做cost[i],
      在一般的0 1背包中都是加上cost[i] ,这里由于是乘法原理,所以应该是转化为乘法。
      这样,传统的01背包累加问题变成了垒乘。
3、状态转移方程:dp[j]=max(dp[j],dp[j-bank[i].money]*bank[i].run)
    dp[j]表示抢j钱时的最大不被抓概率。

//HOJ--2955:Robberies 
#include<iostream>
#include<memory.h>
using namespace std;
struct node
{
   int money;
   float run;//不被抓的概率
}bank[110];
float dp[11000];
int main()
{
    int caseNum,N,sum;
    float risk,limit;
    int i,j;
    
    cin>>caseNum;
    while(caseNum--)
    {
       cin>>limit>>N;
       limit=1.00-limit;//最大被抓概率转化为最小不被抓概率  
       sum=0;
       
       for(i=1;i<=N;i++)
       {
          cin>>bank[i].money>>risk;
          bank[i].run=1.00-risk;//转化为不被抓的概率
          sum=sum+bank[i].money;
       }
       
       memset(dp,0,sizeof(dp));
       dp[0]=1.00;//不抢钱被抓概率为0
       
       for(i=1;i<=N;i++)//抢i银行的钱
       {
          for(j=sum;j>=bank[i].money;j--)
          {
             dp[j]=max(dp[j],dp[j-bank[i].money]*bank[i].run);
          }
       }
       
       for(i=sum;i>=0;i--)//从最大钱数开始枚举
       {
          if(dp[i]>limit)//找到钱数最大而且能成功逃跑的 
          {
             cout<<i<<endl;
             break;
          }
       }
    }
    return 0;
}

          (5)HDU3466:Proud Merchants

题目源地址:http://acm.hdu.edu.cn/showproblem.php?pid=3466

首先对数组按照(limit-price)进行排序,这样保证差额最小为最优,再进行0-1背包。

//HOJ--3466:Proud Merchants
#include<iostream>
#include<memory.h>
#include<algorithm>
using namespace std;
struct node
{
   int price;
   int limit;
   int value;
}item[510];
int cmp(node a,node b)//按limit-price排序,保证差额最小为最优 
{
   return a.limit-a.price < b.limit-b.price;
}
int main()
{
    int N,money;
    int dp[5100];
    int i,j;
    
    while(cin>>N>>money)
    {
       for(i=1;i<=N;i++)
          cin>>item[i].price>>item[i].limit>>item[i].value;
       
       memset(dp,0,sizeof(dp));
       sort(item+1,item+1+N,cmp);
       
       for(i=1;i<=N;i++)
       {
          for(j=money;j>=item[i].limit;j--)//剩余的钱大于limit才能买
          {
                dp[j]=max(dp[j],dp[j-item[i].price]+item[i].value);
          }
       }
       cout<<dp[money]<<endl;
    }
    return 0;
}

(6)HDU1864:最大报销额

问题源地址:http://acm.hdu.edu.cn/showproblem.php?pid=1864

最大报销额这道题有好几种解法。一种是将价格通过乘以100全部化为整数,再利用0-1背包方法解决,简要代码如下:

        for(i = 0; i<=l; i++)  
        {  
            for(j = sum; j>=money[i]; j--)  
                    dp[j] = max(dp[j],dp[j-money[i]]+money[i]);  
        }  
        printf("%.2lf\n",dp[sum]/100.0);  

            之前我自己做这道题的时候没用0-1背包,是直接用的DP求最大和,判定条件比0-1背包解法麻烦了点。

具体解题方法可以参照我之前的博客:http://blog.csdn.net/u012856866/article/details/38920889

附上我之前写的代码:

//HOJ--1864:最大报销额     0MS / 284K
#include<iostream>
#include<stdio.h>
#include<algorithm>
using namespace std;
struct node
{
   float value;
}ticket[35];
int cmp(float a,float b)
{
    return a > b;
}
int main()
{
    int i,j,N,n;  //N:发票张数   n:每张发票上物品数 
    int CanUse[35];  //判断单张发票是否符合要求 
    char kind,ch;  //kind:物品的种类 
    float sum,val;  //sum:总报销值    val:每件物品的价值 
    float sum_A,sum_B,sum_C;  //单类物品的总金额 
    
    while(cin>>sum>>N && N)
    {
       
       for(i=0;i<N;i++)
       {
          CanUse[i]=1;
          ticket[i].value=0;
          
          cin>>n;//输入每张发票上物品件数
          sum_A=sum_B=sum_C=0.00;
          
          for(j=0;j<n;j++)
          {
             cin>>kind>>ch>>val;
             
             if(kind!='A' && kind!='B' && kind!='C')//注意:只有A,B,C三类可以报销 
                CanUse[i]=0;
             else 
             {
                if(kind=='A')        sum_A=sum_A+val;
                else if(kind=='B')   sum_B=sum_B+val;
                else if(kind=='C')   sum_C=sum_C+val;
             }
             
             ticket[i].value=ticket[i].value+val;
          }
          
          if(sum_A>600.00 || sum_B>600.00 || sum_C>600.00)//每张发票上单类物品价值不可超过600
             CanUse[i]=0;
          
          if(ticket[i].value>1000.00 || ticket[i].value>sum)//每张发票价值不可超过1000元 且每张发票总金额不可超过总报销额 
             CanUse[i]=0;
       }
       
       float t[35];
       int len=0;
       
       for(i=0;i<N;i++)
       {
          if(CanUse[i])
          {
             t[len]=ticket[i].value;
             len++;
          }
       }
       
       sort(t,t+len,cmp);//排序函数一定要有,不然上述提到的测试数据会出错! 
       
        //这里最大钱数应该要用动态规划来求,dp[n]=max(dp[n-1]+t[n],t[n]),限制条件为各值都要小于总报销值sum  
       float dp[35];//动态规划数组 
       dp[0]=t[0];
       
       for(i=1;i<len;i++)
       {
          if(dp[i-1]+t[i]>=t[i])
          {
             if(dp[i-1]+t[i]<=sum)
                dp[i]=dp[i-1]+t[i];
                
             else dp[i]=max(dp[i-1],t[i]);//这里也很关键! 
                
          }
       
          else
          {
             dp[i]=ticket[i].value;//ticket[i].value>sum CanUse[i]=0保证了单张发票总额不会超过sum 
          }
          
       }
       //找出dp[]中的最大值
       float max_value=0.00;
       for(i=0;i<len;i++)
       {
          if(dp[i]>max_value)
             max_value=dp[i];
       }
       printf("%.2lf\n",max_value);
    }
    return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值