1. 程式人生 > >動態規劃:最少硬幣找零問題、01揹包問題、完全揹包問題

動態規劃:最少硬幣找零問題、01揹包問題、完全揹包問題

題目一: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])
實現程式碼
#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;//輸出最優解  
  
}  
在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

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]