動態規劃:最少硬幣找零問題、01揹包問題、完全揹包問題
阿新 • • 發佈:2019-01-30
題目一:01揹包問題
一個揹包總容量為V,現在有N個物品,第i個 物品體積為weight[i],價值為value[i],現在往揹包裡面裝東西,怎麼裝能使揹包的內物品價值最大?
題目二:完全揹包問題
一個揹包總容量為V,現在有N個物品,第i個 物品體積為weight[i],價值為value[i],每個物品都有無限多件,現在往揹包裡面裝東西,怎麼裝能使揹包的內物品價值最大?
題目三:最少硬幣找零問題
給予不同面值的硬幣若干種種(每種硬幣個數無限多),如何用若干種硬幣組合為某種面額的錢,使硬幣的的個數最少?
在現實生活中,我們往往使用的是貪心演算法,比如找零時需要13元,我們先找10元,再找2元,再找1元。如果我們的零錢可用的有1、2、5、9、10。我們找零18元時,貪心演算法的策略是:10+5+2+1,四種,但是明明可以用兩個9元的啊。這種問題一般使用動態規劃來解決。
一、首先來看01揹包問題
用一個數組f[i][j]表示,在只有i個物品,容量為j的情況下揹包問題的最優解。第i個物品可以選擇放進揹包或者不放進揹包(這也就是0和1),假設放進揹包(前提是放得下),那麼f[i][j]=f[i-1][j-weight[i]+value[i];如果不放進揹包,那麼f[i][j]=f[i-1][j]。
這就得出了狀態轉移方程:
f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]+value[i])
實現程式碼在hihocoder上面還講到可以進一步優化記憶體使用。上面計算f[i][j]可以看出,在計算f[i][j]時只使用了f[i-1][0……j],i-1沒有變化,j是從0一直遞增,因此可以用一個一維陣列儲存i-1時求得j對應的每個f[j];然後求i時,利用i-1時的陣列f[j]遞推求得到i時f[j],陣列複用同一個。再進一步思考,為了複用陣列時不對資料產生汙染,計算f[j]時應該從後往前算,即 j=M......1#include<iostream> using namespace std; #define V 1500 unsigned int f[10][V];//全域性變數,自動初始化為0 unsigned int weight[10]; unsigned int value[10]; #define max(x,y) (x)>(y)?(x):(y) int main() { int N,M; cin>>N;//物品個數 cin>>M;//揹包容量 for (int i=1;i<=N; i++) { cin>>weight[i]>>value[i]; } for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) { if (weight[i]<=j) { f[i][j]=max(f[i-1][j],f[i-1][j-weight[i]]+value[i]); } else f[i][j]=f[i-1][j]; } cout<<f[N][M]<<endl;//輸出最優解 }
for i=1……N
for j=M……1
f[j]=max(f[j],f[j-weight[i]+value[i])
實現程式碼:
二、完全揹包問題 和 硬幣找零問題#include<iostream> using namespace std; #define V 1500 unsigned int f[V];//全域性變數,自動初始化為0 unsigned int weight[10]; unsigned int value[10]; #define max(x,y) (x)>(y)?(x):(y) int main() { int N,M; cin>>N;//物品個數 cin>>M;//揹包容量 for (int i=1;i<=N; i++) { cin>>weight[i]>>value[i]; } for (int i=1; i<=N; i++) for (int j=M; j>=1; j--) { if (weight[i]<=j) { f[j]=max(f[j],f[j-weight[i]]+value[i]); } } cout<<f[M]<<endl;//輸出最優解 }
其實這個兩個問題非常相似,都是物品數目無限多,一個是不超過某個重量值W求最大value,一個是要獲得某個value,求最小重量(每個硬幣可以看成是重量為1的物品)。
解法1:
(1)對於完全揹包問題狀態轉移方程:
f[i][j]=max(f[i-1][j-k*weight[i]+k*value[i]),其中0<=k<=j/weight[i]
可以理解為:j為揹包可以容納的重量,有i種物品時,向背包裡新增第i種物品,第i種物品可以新增的個數範圍是 0<=k<=j/weight[i]
(2)對於硬幣找零問題狀態轉移方程:
f[i][j]=min(f[i-1][j-k*value[i]+k),其中0<=k<=j/value[i]
可以理解為:j為需要找零多少元,有i種硬幣,找零時選取第i種硬幣,第i種硬幣可以選取的枚數是 0<=k<=j/value[i]