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. }  

理解了01揹包之后,下面就来看看实际的题目

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. }  

 

 

HDU1171:Big Event in HDU

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

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

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. int val[5005];  
  7. int dp[255555];  
  8.   
  9. int main()  
  10. {  
  11.     int n,i,j,a,b,l,sum;  
  12.     while(~scanf(“%d”,&n),n>0)  
  13.     {  
  14.         memset(val,0,sizeof(val));  
  15.         memset(dp,0,sizeof(dp));  
  16.         l = 0;  
  17.         sum = 0;  
  18.         for(i = 0;i<n;i++)  
  19.         {  
  20.             scanf(“%d%d”,&a,&b);  
  21.             while(b–)  
  22.             {  
  23.                 val[l++] = a;//将价值存入数组  
  24.                 sum+=a;  
  25.             }  
  26.         }  
  27.         for(i = 0;i<l;i++)  
  28.         {  
  29.             for(j = sum/2;j>=val[i];j–)//01揹包  
  30.             {  
  31.                 dp[j] = max(dp[j],dp[j-val[i]]+val[i]);  
  32.             }  
  33.         }  
  34.         printf(“%d %d\n”,sum-dp[sum/2],dp[sum/2]);  
  35.     }  
  36.   
  37.     return 0;  
  38. }  

HDU2602:Bone Collector

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

经典的01揹包题,给出了石头的数量与揹包的容量,然后分别给出每个石头的容量与价值,要求最优解,经过前面的练手,这道题已经是很简单了,可以说是01揹包果题。

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. struct Node  
  7. {  
  8.     int h;  
  9.     int v;  
  10. } node[1005];  
  11.   
  12. int main()  
  13. {  
  14.     int t,n,m,l;  
  15.     int dp[1005];  
  16.     scanf(“%d”,&t);  
  17.     while(t–)  
  18.     {  
  19.         scanf(“%d%d”,&n,&m);  
  20.         int i;  
  21.         for(i = 1; i<=n; i++)  
  22.             scanf(“%d”,&node[i].h);  
  23.         for(i = 1; i<=n; i++)  
  24.             scanf(“%d”,&node[i].v);  
  25.         memset(dp,0,sizeof(dp));  
  26.         for(i = 1; i<=n; i++)  
  27.         {  
  28.             for(l = m; l>=node[i].v; l–)  
  29.                 dp[l] = max(dp[l],dp[l-node[i].v]+node[i].h);  
  30.         }  
  31.         printf(“%d\n”,dp[m]);  
  32.     }  
  33.   
  34.     return 0;  
  35. }  

HDU2639:Bone Collector II(01揹包第k优解)

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

解决了上面那倒题目之后,这道题跟上面的题目有些不同,因为这里要求的是第K优解

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. struct Node  
  7. {  
  8.     int price;  
  9.     int val;  
  10. } node[1005];  
  11.   
  12. int main()  
  13. {  
  14.     int t;  
  15.     scanf(“%d”,&t);  
  16.     while(t–)  
  17.     {  
  18.         int n,v,k,i,dp[1005][31] = {0},a[31],b[31];  
  19.         scanf(“%d%d%d”,&n,&v,&k);  
  20.         for(i = 0; i<n; i++)  
  21.             scanf(“%d”,&node[i].price);  
  22.         for(i = 0; i<n; i++)  
  23.             scanf(“%d”,&node[i].val);  
  24.         int j;  
  25.         for(i = 0; i<n; i++)  
  26.         {  
  27.             for(j = v; j>=node[i].val; j–)  
  28.             {  
  29.                 int cnt = 0,d;  
  30.                 for(d = 1; d<=k; d++)//分别将放入第i个石头与不放第i个石头的结果存入a,b,数组之中  
  31.                 {  
  32.                     a[d] = dp[j-node[i].val][d]+node[i].price;  
  33.                     b[d] = dp[j][d];  
  34.                 }  
  35.                 int x,y,z;  
  36.                 x = y = z = 1;  
  37.                 a[d] = b[d] = -1;  
  38.                 while(z<=k && (x<=k || y<=k))//循环找出前K个的最优解  
  39.                 {  
  40.                     if(a[x] > b[y])  
  41.                     {  
  42.                         dp[j][z] = a[x];  
  43.                         x++;  
  44.                     }  
  45.                     else  
  46.                     {  
  47.                         dp[j][z] = b[y];  
  48.                         y++;  
  49.                     }  
  50.                     if(dp[j][z]!=dp[j][z-1])  
  51.                     z++;  
  52.                 }  
  53.             }  
  54.         }  
  55.         printf(“%d\n”,dp[v][k]);  
  56.     }  
  57.   
  58.     return 0;  
  59. }  

 

HDU2955:Robberies

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

这道题有点特别,咋看之下其状态转移方程似乎有些不同,但事实上远离是相通的,要注意其精度

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <algorithm>  
  3. using namespace std;  
  4.   
  5. struct Bank  
  6. {  
  7.     int money;  
  8.     double p;  
  9. } bank[10005];  
  10.   
  11. int main()  
  12. {  
  13.     int n,t;  
  14.     double p;  
  15.     scanf(“%d”,&t);  
  16.     while(t–)  
  17.     {  
  18.         scanf(“%lf%d”,&p,&n);  
  19.         p = 1-p;  
  20.         int i,j,sum = 0;  
  21.         for(i = 0; i<n; i++)  
  22.         {  
  23.             scanf(“%d%lf”,&bank[i].money,&bank[i].p);  
  24.             bank[i].p = 1-bank[i].p;  
  25.             sum+=bank[i].money;  
  26.         }  
  27.         double dp[10005]= {1.0};  
  28.         for(i = 0; i<n; i++)  
  29.         {  
  30.             for(j = sum; j>=bank[i].money; j–)  
  31.             {  
  32.                 dp[j] = max(dp[j],dp[j-bank[i].money]*bank[i].p);  
  33.             }  
  34.         }  
  35.         for(i = sum; i>=0; i–)  
  36.         {  
  37.             if(dp[i]-p>0.000000001)  
  38.             {  
  39.                 printf(“%d\n”,i);  
  40.                 break;  
  41.             }  
  42.         }  
  43.     }  
  44.   
  45.     return 0;  
  46. }  

HDU3466:Proud Merchants

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

这道题由于规定了手上的前低于q时就不能购买该样东西,所以要先将商品按q-p排序,剩下的就是简单的01揹包了

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <algorithm>  
  4. using namespace std;  
  5.   
  6. struct node  
  7. {  
  8.     int p,q,v;  
  9. } a[555];  
  10.   
  11. int cmp(node x,node y)//按q-p排序,保证差额最小为最优  
  12. {  
  13.     return x.q-x.p<y.q-y.p;  
  14. }  
  15.   
  16. int main()  
  17. {  
  18.     int n,m,i,j;  
  19.     int dp[5555];  
  20.     while(~scanf(“%d%d”,&n,&m))  
  21.     {  
  22.         for(i = 0; i<n; i++)  
  23.             scanf(“%d%d%d”,&a[i].p,&a[i].q,&a[i].v);  
  24.         memset(dp,0,sizeof(dp));  
  25.         sort(a,a+n,cmp);  
  26.         for(i = 0; i<n; i++)  
  27.         {  
  28.             for(j = m; j>=a[i].q; j–)//剩余的钱大于q才能买  
  29.             {  
  30.                 dp[j] = max(dp[j],dp[j-a[i].p]+a[i].v);//这里的j-a[i].p决定了之前的排序方法  
  31.             }  
  32.         }  
  33.         printf(“%d\n”,dp[m]);  
  34.     }  
  35.   
  36.     return 0;  
  37. }  

 

HDU1864:最大报销额

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

题目中药注意的有几样,首先每张发票中单件物品价格不能超过600,其次发票总额不能超过1000,而且发票上的物品必须是ABC三类,将满足以上条件的发票存入数组之中,就是裸01揹包

[cpp] 
view plain
 copy

  1. #include <stdio.h>  
  2. #include <algorithm>  
  3. #include <string.h>  
  4. using namespace std;  
  5.   
  6. int dp[3000050];//由于每张发票不超过1000,最多30张,扩大100倍数后开这么大即可  
  7.   
  8. int main()  
  9. {  
  10.     char ch;  
  11.     double x,y;  
  12.     int sum,a,b,c,money[35],v;  
  13.     int t,i,j,k;  
  14.     while(~scanf(“%lf%d”,&x,&t),t)  
  15.     {  
  16.         sum = (int)(x*100);//将小数化作整数处理  
  17.         memset(money,0,sizeof(money));  
  18.         memset(dp,0,sizeof(dp));  
  19.         int l = 0;  
  20.         for(i = 0; i<t; i++)  
  21.         {  
  22.             scanf(“%d”,&k);  
  23.             a = b = c = 0;  
  24.             int flag = 1;  
  25.             while(k–)  
  26.             {  
  27.                 scanf(” %c:%lf”,&ch,&y);  
  28.                 v = (int)(y*100);  
  29.                 if(ch == ‘A’ && a+v<=60000)  
  30.                     a+=v;  
  31.                 else if(ch == ‘B’ && b+v<=60000)  
  32.                     b+=v;  
  33.                 else if(ch == ‘C’ && c+v<=60000)  
  34.                     c+=v;  
  35.                 else  
  36.                     flag = 0;  
  37.             }  
  38.             if(a+b+c<=100000 && a<=60000 && b<=60000 && c<=60000 && flag)//按题意所说,必须满足这些条件  
  39.                 money[l++] = a+b+c;  
  40.         }  
  41.         for(i = 0; i<=l; i++)  
  42.         {  
  43.             for(j = sum; j>=money[i]; j–)  
  44.                     dp[j] = max(dp[j],dp[j-money[i]]+money[i]);  
  45.         }  
  46.         printf(“%.2lf\n”,dp[sum]/100.0);  
  47.     }  
  48.   
  49.     return 0;  
  50. }  
点赞