01背包問題詳解


引言

背包問題是動態規划(DP)的一類問題。

背包問題的核心其實就是組合問題,在一個背包中有若干物品,在某種限制條件下,選出最好的組合。

01背包問題

特點:每件物品最多只能用一次。

有 N 件物品和一個容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的體積是 vi,價值是 wi。
求解將哪些物品裝入背包,可使這些物品的總體積不超過背包容量,且總價值最大。
輸出最大價值。

輸入格式
第一行兩個整數,N,V,用空格隔開,分別表示物品數量和背包容積。
接下來有 N 行,每行兩個整數 vi,wi,用空格隔開,分別表示第 i 件物品的體積和價值。

輸出格式
輸出一個整數,表示最大價值。

數據范圍
0<N,V≤1000
0<vi,wi≤1000
輸入樣例
4 5
1 2
2 4
3 4
4 5
輸出樣例:
8

思路:

如果采用暴力枚舉每一件物品放或者不放進背包,有兩種選擇,所以時間復雜度為\(O(2^n)\),非常大。

接下來考慮動態規划求解。

題解一:先嘗試二維解法。

我們可以定義一個二維數組dp存儲最大價值,其中dp[i][j] 表示前i 件物品體積不超過j (即此時背包容量

為j)的情況下能達到的最大價值。

在我們遍歷到第i 件物品時,在當前背包總容量為j 的情況下,

  1. 如果我們不將物品i 放入背包,那么dp[i][j]= dp[i-1][j],即前i 個物品的最大價值等於只取前i-1 個

    物品時的最大價值;

  2. 如果我們將物品i 放入背包,假設第i 件物品體積為wi,價值為vi,那么我們得到

    dp[i][j] = dp[i-1][j-w[i]] + v[i]。我們只需在遍歷過程中對這兩種情況取最大值即可,總時間復雜度和空間復雜度都為\(O(NV)\)

綜合上面提到的2種選擇策略,我們可以得到狀態轉移方程

dp[i][j] = max{dp[i-1][j],dp[i-1][j-w[i]] + v[i]}

確定初始化邊界,dp[0][0] = 0 .

注意理解誤區

dp[i][j]里的i不是表示選擇了前i個物品,而是表示對前i個物品做出兩中策略的選擇;

里面的j不是表示當前物品的總體積等於j,而是表示前i 件物品體積不超過j 。

代碼:(二維朴素做法)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int dp[N][N]; // dp[0][0] = 0
int v[N],w[N];
int n,m;

int main(){
    cin >> n >> m;
    
    for (int i = 1;i <= n;i++) cin >> v[i] >> w[i];
    
    for (int i = 1;i <= n;i++)
        for (int j = 0;j <= m;j++){
            dp[i][j] = dp[i-1][j];
            if (j - v[i] >= 0){
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
            }
        }
    
    cout << dp[n][m];
    return 0;
}

題解二:再嘗試一維優化。

也就是對二維做法等價變形得到一維做法。

我們可以進一步對0-1 背包進行空間優化,將空間復雜度降低為\(O(V)\)。時間復雜度已經不能再優化了。

從二維變成一維,相當於把二維中第一個維度變成循環滾動只有1行的數組dp[N]

如果我們仍然從左往右計算dp[j],那么可能存在污染,因為后面的數據根據前面遞推而來,在滾動的時候可能要用到dp[i-1](即上一次循環的數據時,實際上這個位置的數據已經在這次循環時被更新過了,用到的是dp[i]的數據,那么就出錯了。

只有通過逆序枚舉v,即從右往左滾動數組,這次計算dp[i]時依然根據上次循環遞推而來,而且dp[i-v[i]]並沒有被污染,才能得到正確結果。

我們注意到在處理數據時,我們是一個物品一個物品,一個一個體積的枚舉。

因此我們可以不必開兩個數組記錄體積和價值,而是邊輸入邊處理。這樣可以進一步壓縮空間。

代碼:

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int dp[N];
int n,m;
int v,w;
int main(){
    cin >> n >> m;
    for (int i = 1;i <= n;i++){
        cin >> v >> w; // 邊輸入邊處理
        for (int j = m;j >= v;j--){
            dp[j] = max(dp[j],dp[j-v]+w);
        }
    }    
    cout << dp[m];
    return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM