1. 程式人生 > >01揹包的理解,二維陣列化一維陣列的理解(附hdu2602 Bone Collector)

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;
}