动态规划专题 01背包问题详解 HDU 2546 饭卡

我以此题为例,详细分析01背包问题,希望该题能够为初学者对01背包问题的理解有所帮助,有什么问题可以向我提供,一同进步^_^

饭卡

Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 14246    Accepted Submission(s):
4952

Problem Description

电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。

Input

多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。

Output

对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。

Sample Input

1
50
5
10
1 2 3 2 1 1 2 3 2 1
50
0

Sample Output

-45
32

Source

UESTC
6th Programming Contest Online

这条题目里,我们要先注意要达到最小余额,那么最大的菜价一定是最后要减的,那么我们将这一组饭菜价格按从小到大排序,将最大的那个先放一边,我们接下来就是要把剩下的一些菜价用我们手头的余额减,当然必须要保证减去的金额小于等于sum-5,这样我们才能在最后一次把最大的菜价刷掉。

我们做的转化就是,把除了最大菜价之外,其他的菜价装入一个sum-5 的背包里,看最大能装多少。

首先基于上一篇我们的理论。(很重要!)

【理论讲解】http://www.cnblogs.com/fancy-itlife/p/4393213.html

首先看第一个条件—最优子结构。最大的装入量一定是如果装入第i个或者不装入第i个的两个选择之一。

第二个条件—子问题重叠。当完成一个阶段比如装第i个,我下面做的是对i-1个进行抉择,你可以发现跟前面的问题一样,装还是不装两个选择之一。这就是所谓的子问题重叠。

第三个条件—边界。这样的选择总归要有个结束的时候,当他到了第一个菜价时,如果它的背包容量也就是余额大于菜价,一定要装进去啊,这才会有可能变得比较大。如果不够的话,那一定是0。至此选择全部结束,然后是递归地返回上一层,直至抉择出正确答案。

第四个条件—子问题独立。装还是不装两个选择,双方的选择不会影响对方。

下面我们就要来考虑一下,第五个条件—备忘录,也就是记忆化搜索,如果这个结果的值已经得到,那么我们把它记录下来,以便后面再出现该值时直接使用。那么对于此问题的独立的小问题的就是执行了前n个菜价的抉择(装或不装),余额还剩m时的最大容量。可以用一个二维数组表示n*m

那么上面已经详细叙述了该问题的求解方式,用记忆化的方式先来实现一下!

 代码

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define MAXN 1005
 6 using namespace std;
 7 int price[MAXN];
 8 int total[MAXN][MAXN];
 9 int dfs(int m,int k)//利用记忆化搜索实现01背包
10 {
11     int s;
12     if(total[m][k]>=0)//如果该值已经被记录了那么直接返回
13         return total[m][k];
14     if(k==1)//处理边界值
15     {
16         if(m>=price[1])//如果剩余的额度大于等于该菜价,那么一定返回要将该菜价赋给s
17             s=price[1];
18         else//如果剩余的额度小于该菜价,那么一定返回0
19             s=0;
20     }
21     else if(m>=price[k])//如果此时的额度是大于等于当前的菜价,则是这两种选择之中的一个
22         s=max((dfs(m-price[k],k-1)+price[k]),dfs(m,k-1));
23     else//如果此时的额度是小于当前的菜价,则仅考虑不买这个菜的情况!
24         s=dfs(m,k-1);
25     total[m][k]=s;//记忆化
26     return s;
27 }
28 int main()
29 {
30     int n,i,sum,s;
31     while(scanf("%d",&n)!=EOF)
32     {
33         if(n==0)
34             break;
35         memset(total,-1,sizeof(total));
36         for(i=1;i<=n;i++)
37             scanf("%d",&price[i]);
38         scanf("%d",&sum);
39         if(sum<=4)
40             printf("%d\n",sum);
41         else
42         {
43             sort(price+1,price+n+1);
44             s=sum;
45             sum=dfs(sum-5,n-1);
46             sum=s-sum-price[n];
47             printf("%d\n",sum);
48         }
49     }
50     return 0;
51 }

但其实记忆化搜索的方式,比较适合初学时理解,但是其实它的不足在于递归开销太大,效率不算很高。

接下来我们试着用递推的方式来实现该过程其实我们完全可以将每一个子问题由小到大不断由前面的已解决的问题中推出,比如只有一个菜价时,根据余额和菜价的关系直接就可以得到最大的价值,(这也一定是正确且最大的)到达第二个菜价时,我们抉择的还是装还是不装,装的话,我们要把余额减去第二个菜价看看还剩的钱在前一个选择面前我们能获得的最大金额再加上第二个菜价与不装第二个菜的最大金额比较大小,那么不装第二个菜,那就是第一个菜在这种余额下的最大金额。那么由于第一个阶段是满足最优的,那么你通过两种选择,也就得到了第二个阶段的最有情况。那么往复这样的情况我们就获得了递推式的01背包求解。

 代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define MAXN 1005
 6 using namespace std;
 7 int price[MAXN];
 8 int total[MAXN][MAXN];
 9 int main()
10 {
11     int n,m,i,j,s,sum;
12     while(scanf("%d",&n)!=EOF)
13     {
14         if(n==0)
15             break;
16         memset(total,0,sizeof(total));
17         for(i=1;i<=n;i++)
18             scanf("%d",&price[i]);
19         scanf("%d",&sum);
20         if(sum<=4)
21             printf("%d\n",sum);
22         else
23         {
24             sort(price+1,price+n+1);
25             for(i=0;i<=sum-5;i++)
26             {
27                 if(i<price[1])
28                     total[1][i]=0;
29                 else
30                      total[1][i]=price[1];
31             }
32             for(i=2;i<=n-1;i++)//i表示依次选取前n个菜品(标号)
33                 for(j=0;j<=sum-5;j++)//j表示余额
34                 {
35                     if(j<price[i])
36                         total[i][j]=total[i-1][j];
37                     else
38                         total[i][j]=max(total[i-1][j-price[i]]+price[i],total[i-1][j]);
39                 }
40             s=0;
41             for(i=1;i<=n-1;i++)
42                 for(j=0;j<=sum-5;j++)
43                 {
44                     if(s<total[i][j])
45                         s=total[i][j];
46                 }
47             //cout<<s<<" "<<price[n]<<endl;
48             sum=sum-s-price[n];
49             printf("%d\n",sum);
50         }
51     }
52     return 0;
53 }

那么我们还可以再将空间减少为一维数组,原因是什么呢,代码的注释里详细的解释了。

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #define MAXN 1005
 6 using namespace std;
 7 int price[MAXN];
 8 int total[MAXN];
 9 int main()
10 {
11     int n,m,i,j,s,sum;
12     while(scanf("%d",&n)!=EOF)
13     {
14         if(n==0)
15             break;
16         memset(total,0,sizeof(total));
17         for(i=1;i<=n;i++)
18             scanf("%d",&price[i]);
19         scanf("%d",&sum);
20         if(sum<=4)
21             printf("%d\n",sum);
22         else
23         {
24             sort(price+1,price+n+1);
25             //为什么只要用到一维数组,因为它的第二维只跟前一阶段有关,
26             //那么用一维数组就可以保存一个阶段的值,下一个阶段用上一个阶段来更新
27             for(i=1;i<=n-1;i++)//前n个阶段
28                 for(j=sum-5;j>=0;j--)//表示此时该阶段如果为有j余额
29                 {
30                     if(j>=price[i])
31                         total[j]=max(total[j-price[i]]+price[i],total[j]);
32                         /*为什么需要逆序因为逆序可以带来的正确性是不言而喻的
33                         我需要将前一阶段的j-price[i]余额的最大的消费获取到,
34                         如果正向的话,我在求取一些余额较大的值时可能获得了该阶段
35                         的j-price[i]的最大的消费额,因为小的余额是先更新的。
36                         */
37                 }
38             s=0;
39             for(j=1;j<=sum-5;j++)
40             {
41                 if(s<total[j])
42                     s=total[j];
43             }
44             sum=sum-s-price[n];
45             printf("%d\n",sum);
46         }
47     }
48     return 0;
49 }

再看看这三题的时间空间效率对比

自己也是才接触这类动态规划问题,也希望此篇博文对大家学习01背包有所帮助!

时间: 2024-10-12 23:23:51

动态规划专题 01背包问题详解 HDU 2546 饭卡的相关文章

动态规划专题 01背包问题详解【转】

对于动态规划,每个刚接触的人都需要一段时间来理解,特别是第一次接触的时候总是想不通为什么这种方法可行,这篇文章就是为了帮助大家理解动态规划,并通过讲解基本的01背包问题来引导读者如何去思考动态规划.本文力求通俗易懂,无异性,不让读者感到迷惑,引导读者去思考,所以如果你在阅读中发现有不通顺的地方,让你产生错误理解的地方,让你难得读懂的地方,请跟贴指出,谢谢! 初识动态规划 经典的01背包问题是这样的: 有一个包和n个物品,包的容量为m,每个物品都有各自的体积和价值,问当从这n个物品中选择多个物品放

HDU 2546 饭卡 01背包问题

01背包问题,增加一个额外条件,余额大于等于5的时候,可以购买任意价格的东西,那么就把5保留下来买最大价格的物品. 然后转化为收益的时候要注意初始条件,即没买东西的时候收益就是余额,就是所有的钱.这个还是有点难转换思维的. 最后是要熟悉背包填表,不需要保留选择的时候,就逆向填表,只需要一维表就可以了,当然也可以使用滚动数组,两个一维数组就可以了. 走了算法一圈回来了,AC自动机,线段树等都学会了,但是遇上这些基础DP还是不太轻松,看来需要继续打打基础.主要是思维转换不够流畅,速度就不快了. #i

hdu 2546 饭卡(0-1背包)

题目来源:hdu 2546 饭卡 饭卡 Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 16645 Accepted Submission(s): 5797 Problem Description 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额.如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后

hdu 2546 饭卡

0-1背包问题 i = 1,扫所有上限价钱只购买一件物品的最大消费(price[1]) i = 2,更新一遍,此时是购买两件物品的最大消费(price[2]) 以此类推~有n件物品 但是只进行到n-1,是因为最大的那件物品留至最后才减 价值最高上限为m-5 1 #include<iostream> 2 #include<memory.h> 3 #include<algorithm> 4 using namespace std; 5 int price[1010]; 6

动态规划之01背包详解【解题报告】

01背包问题,是用来介绍动态规划算法最经典的例子,网上关于01背包问题的讲解也很多,我写这篇文章力争做到用最简单的方式,最少的公式把01背包问题讲解透彻. 01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ),  f[i-1,j] } f[i,j]表示在前i件物品中选择若干件放在承重为 j 的背包中,可以取得的最大价值. Pi表示第i件物品的价值. 决策:为了背包中物品总价值最大化,第 i件物品应该放入背包中吗 ? 题目描述: 有编号分别为a,b

HDU 2546 饭卡(01 背包)

链接:http://acm.hdu.edu.cn/showproblem.php?pid=2546 思路:需要首先处理一下的的01背包,当饭卡余额大于等于5时,是什么都能买的,所以题目要饭卡余额最小,那预留5元(相当于饭卡余额为5)来买最贵的菜 然后对剩下n-1进行01背包dp才是正确的.但是还存在一个问题,那就饭卡初始余额小于5时,也要处理掉. 下面讲01背包(原型可以看大牛的背包九讲,本人也正在学习),定义dp[i][j]为买前i种菜品剩下j元时的最大消费值等于下面两中情况之一的值 有两种来

HDU 2546 饭卡(01背包裸题)

饭卡 Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 28562    Accepted Submission(s): 9876 Problem Description 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额.如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无

HDU 2546 饭卡(0-1背包)

http://acm.hdu.edu.cn/showproblem.php?pid=2546 题意: 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额.如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够).所以大家都希望尽量使卡上的余额最少. 某天,食堂中有n种菜出售,每种菜可购买一次.已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少. 思路:我们先用一个背包容量为price-5的背包去装菜,背包里

hdu 2546 饭卡(01背包)

Problem Description 电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额.如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够).所以大家都希望尽量使卡上的余额最少. 某天,食堂中有n种菜出售,每种菜可购买一次.已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少. Input 多组数据.对于每组数据: 第一行为正整数n,表示菜的数量.n<=1000. 第二行包括n个正整数,表示每种菜的价