01揹包問題和完全揹包問題
在hihocoder上面的題目中看到的這個問題,總結一下。先看01揹包問題。
01揹包問題:一個揹包總容量為V,現在有N個物品,第i個 物品體積為weight[i],價值為value[i],現在往揹包裡面裝東西,怎麼裝能使揹包的內物品價值最大?
看到這個問題,可能會想到貪心演算法,但是貪心其實是不對的。例如最少硬幣找零問題,要用動態規劃。動態規劃思想就是解決子問題並記錄子問題的解,這樣就不用重複解決子問題了。
動態規劃先找出子問題,我們可以這樣考慮:在物品比較少,揹包容量比較小時怎麼解決?用一個數組f[i][j]表示,在只有i個物品,容量為j的情況下揹包問題的最優解,那麼當物品種類變大為i+1時,最優解是什麼?第i+1個物品可以選擇放進揹包或者不放進揹包(這也就是0和1),假設放進揹包(前提是放得下),那麼f[i+1][j]=f[i][j-weight[i+1]+value[i+1];如果不放進揹包,那麼f[i+1][j]=f[i][j]。
這就得出了狀態轉移方程:
f[i+1][j]=max(f[i][j],f[i][j-weight[i+1]+value[i+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;//輸出最優解 }
在hihocoder上面還講到可以進一步優化記憶體使用。上面計算f[i][j]可以看出,在計算f[i][j]時只使用了f[i-1][0……j],沒有使用其他子問題,因此在儲存子問題的解時,只儲存f[i-1]子問題的解即可。這樣可以用兩個一維陣列解決,一個儲存子問題,一個儲存正在解決的子問題。
再進一步思考,計算f[i][j]時只使用了f[i-1][0……j],沒有使用f[i-1][j+1]這樣的話,我們先計算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;//輸出最優解 }
在看完01揹包問題,再來看完全揹包問題:一個揹包總容量為V,現在有N個物品,第i個 物品體積為weight[i],價值為value[i],每個物品都有無限多件,現在往揹包裡面裝東西,怎麼裝能使揹包的內物品價值最大?
對比一下,看到的區別是,完全揹包問題中,物品有無限多件。往揹包裡面新增物品時,只要當前揹包沒裝滿,可以一直新增。那麼狀態轉移方程為:
f[i+1][j]=max(f[i][j-k*weight[i+1]]+k*value[i+1]),其中0<=k<=V/weight[i+1]
使用記憶體為一維陣列,虛擬碼
for i=1……N
for j=1……M
f[j]=max(f[j],f[j-weight[i]+value[i])
和01揹包問題唯一不同的是j是從1到M。01揹包問題是在前一個子問題(i-1種物品)的基礎上來解決當前問題(i種物品),向i-1種物品時的揹包新增第i種物品;而完全揹包問題是在解決當前問題(i種物品),向i種物品時的揹包新增第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=1; j<=M; j++)
{
if (weight[i]<=j)
{
f[j]=max(f[j],f[j-weight[i]]+value[i]);
}
}
cout<<f[M]<<endl;//輸出最優解
}