动态规划专题 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


php

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
 

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

咱们作的转化就是,把除了最大菜价以外,其余的菜价装入一个sum-5 的背包里,看最大能装多少。ios

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

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

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

第二个条件—子问题重叠。当完成一个阶段好比装第i个,我下面作的是对i-1个进行抉择,你能够发现跟前面的问题同样,装仍是不装两个选择之一。这就是所谓的子问题重叠。设计

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

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

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

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

 代码

 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背包有所帮助!

相关文章
相关标签/搜索