Time Limit: 5000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 14246 Accepted Submission(s): 4952
php
這條題目裏,咱們要先注意要達到最小余額,那麼最大的菜價必定是最後要減的,那麼咱們將這一組飯菜價格按從小到大排序,將最大的那個先放一邊,咱們接下來就是要把剩下的一些菜價用咱們手頭的餘額減,固然必需要保證減去的金額小於等於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*m。blog
那麼上面已經詳細敘述了該問題的求解方式,用記憶化的方式先來實現一下!
代碼
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揹包有所幫助!