01揹包的理解,二維陣列化一維陣列的理解(附hdu2602 Bone Collector)
01揹包問題:
有n個物品和一個容量為v的揹包,用val[i]表示第i個物品的價值,用vol[i]表示第i個物品的體積,那麼,如何使揹包裡裝的物品的總價值最大呢?
貪心是不行的,舉個反例:
n=3, v=100
val[i] | vol[i] |
---|---|
80 | 60 |
50 | 50 |
50 | 50 |
按照val[i]/vol[i]比值從大到小貪心,那麼會得到錯誤答案80,但是正確答案是100
動態規劃的思想:
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=0; j<=v; j++){
if (j>=vol[i])
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i]);
else
dp[i+1][j] = dp[i][j];
}
}
如何理解這段程式碼呢?我們設dp[i][j]為前i件物品放在容量為j的揹包裡所能得到的最大價值,那麼思考一下,每個物品都只有兩種可能,放或不放,這就是為什麼叫做01揹包的原因。那麼,我們將第i件物品放在容量為j的揹包中,使dp[i][j]最大,那麼對於第i件物品,也只有兩種操作。於是,我們可以很容易想到,我們不放第i件物品時,是不是需要先知道dp[i-1][j]的值呢?這就形成了dp,形成了一種遞推關係。另一種可能,假設我們放第i件物品,那麼,首先需要考慮當前容量j是否放得下物品i,假設放得下,將j減去vol[i],即在剩餘的體積放前i件所得到的最大的價值為dp[i][j-vol[i]],所以總價值為dp[i][j-vol[i]] +val[i]。
那麼,狀態轉移方程為:
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i])
或者:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-vol[i]]+val[i])
這兩種寫法要注意陣列是從0開始還是從1開始
明顯的,時間複雜度是O(n*v)
但是我們還能將空間複雜度降低,從二維降為一維。
看下面這段程式碼:
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=v; j>=vol[i]; j--){
dp[j] = max(dp[j], dp[j-vol[i]]+val[i])
}
}
如何理解二維降一維呢?對於外層迴圈中的每一個i值,其實都是不需要記錄的,在第i次迴圈時,所有的dp[0…v]都還未更新時,dp[j]還記錄著前i-1個物品在容量為j時的最大價值,這樣就相當於還記錄著dp[i-1][j]和dp[i-1][j-vol[i]]+val[i]。
為什麼要從v開始遞減遍歷?我舉個例子,假設一個物品GG價值1000,體積為2,那麼假設我們按【0…..v】這個順序遍歷,那麼在j=2時,dp[2] = max(dp[2], dp[0]+1000),那麼dp[2] = 1000,當j=4時,dp[4]=max(dp[4], dp[2]+1000), dp[4] = 2000,這時我們再思考一下,GG將被放進揹包兩次!!,如果我們逆序遍歷,就可以避免這種結果。
此外,這裡可以進行一個常數優化,將j>=vol[i]寫進for迴圈中。
大家可以看一下hdu2602這一題,是一題單純的01揹包。
題目:
Bone Collector
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
用二維陣列解:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1000+10;
int val[maxn];
int vol[maxn];
int dp[maxn][maxn];
int main(){
int t, n, v;
cin>>t;
while(t--){
cin>>n>>v;
memset(vol, 0, sizeof(vol));
memset(val, 0, sizeof(val));
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++)
cin>>val[i];
for(int i=0; i<n; i++)
cin>>vol[i];
for(int i=0; i<n; i++){
for(int j=0; j<=v; j++){
if(j>=vol[i])
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i]);
else
dp[i+1][j] = dp[i][j];
}
}
cout<<dp[n][v]<<endl;
}
return 0;
}
用一維陣列解:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1000+10;
int val[maxn];
int vol[maxn];
int dp[maxn];
int main(){
int t, n, v;
cin>>t;
while(t--){
cin>>n>>v;
memset(vol, 0, sizeof(vol));
memset(val, 0, sizeof(val));
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++)
cin>>val[i];
for(int i=0; i<n; i++)
cin>>vol[i];
for(int i=0; i<n; i++){
for(int j=v; j>=vol[i]; j--){
dp[j] = max(dp[j], dp[j-vol[i]]+val[i]);
}
}
cout<<dp[v]<<endl;
}
return 0;
}