1. 程式人生 > 其它 >完全揹包問題詳解

完全揹包問題詳解

本文詳細整理了常見的幾類揹包問題的多種解法。內容涉及01揹包問題,完全揹包問題,多重揹包問題,分組揹包問題等。

引言

揹包問題是動態規劃(DP)的一類問題。

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

完全揹包問題

特點:每件物品有無限個。

有 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
輸出樣例:
10

y氏DP分析法:參考自AcWing閆學燦。

三重迴圈(樸素)做法:資料加強後TLE,重在理解寫法。

// y總題解
// 注意:TLE
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int n,m;
int dp[N][N];
int v[N],w[N];
int main(){
    cin >> n >> m;

    for (int i = 1;i <= n;i++) cin >> v[i] >> w[i];

    // i從1開始列舉,j從0開始列舉
    for (int i = 1;i <= n;i++)
        for (int j = 0;j <= m;j++)
            for (int k = 0;k*v[i]<=j;k++)
                dp[i][j] = max(dp[i][j],dp[i-1][j-k*v[i]] + k*w[i]);
    cout << dp[n][m] << endl;
    return 0;
}

優化:

時間複雜度:O(n*m)。

減少一重迴圈。

對比01揹包問題的狀態轉移方程是:f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i])

我們很容易發現,01揹包和完全揹包的區別就在於第二項的第一維,前者是i-1,而後者是i

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int n,m;
int dp[N][N];
int v[N],w[N];
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]) dp[i][j] = max(dp[i][j],dp[i][j-v[i]]+w[i]);
        }
        
    cout << dp[n][m] << endl;
    return 0;
}

因為和01揹包程式碼很相像,我們很容易想到進一步優化。

這裡先介紹降低第一維度的題解,在01揹包中沒有提到過。

就是將第一個維度直接&1,那麼資料就會儲存在dp[0][x]dp[1][x]中。只要用到dp[2][N]這麼大的陣列就足夠了。(這就是一個兩層的滾動陣列)

我們還可以再優化,邊讀入邊處理。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int n,m;
int dp[2][N];
int v,w;
int main(){
    cin >> n >> m;

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

接下來是類似01揹包的更優化的滾動陣列。

利用滾動陣列優化成一維:

由於完全揹包用到的dp[i][j-v[i]]是第i(即本次)次的結果,不像01揹包一樣用到的是上一次的結果,所以可以直接正向列舉。

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int n,m;
int dp[N];
int v[N],w[N];
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 = v[i];j <= m;j++){
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        }
        
    cout << dp[m] << endl;
    return 0;
}