動態規劃專題 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揹包有所幫助!

相關文章
相關標籤/搜索