揹包問題解題方法總結

旺仔真知棒發表於2020-07-20

最近在牛客刷題遇到好幾道揹包問題,索性這兩天集中火力刷了一些這類的題。這裡總結一下0-1揹包完全揹包多重揹包三種基本的揹包問題的解題套路。(均基於動態規劃的思想)

0-1揹包

題目:有 N 件物品和容量為 W 的揹包。第 i 件物品的重量為 w_i,價值為 v_i,求將不超過揹包容量的物品裝入揹包能得到的最大價值。

特點,每件物品的數量只有一個,可以選擇放或不放某件物品。

dp[i][j]表示將前 i-1 件總重量不超過 j 的物品放入揹包能獲得的最大價值,則可以用以下的轉移方程來表示這個過程:

\[dp[i,j] = max(dp[i - 1, j], dp[i-1, j-w[i]] + v[i]) \]

核心程式碼如下:

for(int i = 0; i < N; i++){
    for(int j = w[i]; j <= W; j++){
        dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
    }
}

注意到dp陣列第i行的值更新只跟 i-1 行有關,因此可以通過滾動陣列或者反向更新的方式優化一下空間複雜度,在動態規劃解題的時候這是一種常用的空間複雜度優化方式。優化後的程式碼如下:

for(int i = 0; i < N; i++){
    // 注意到這裡dp需要從後往前更新,避免更新前就把舊值覆蓋
    // 從實際意義上來說,因為每件物品只有一個,從後向前更新保證了更新是在還沒放入過當前物品的前提下進行的
    for(int j = W; j >= w[i]; j--){  
        dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }
}

完全揹包

題目:有 N 種物品和容量為 W 的揹包。第 i 種物品的重量為 w_i,價值為 v_i,每種物品的數量無限。求將不超過揹包容量的物品裝入揹包能得到的最大價值。

特點:每種物品的數量無限多。

考慮到每種物品的數量無限。用 dp[j] 表示在重量不超過 j 的情況下揹包中物品可以達到的最大價值,則轉移方程如下:

\[dp[j]=max(dp[j], dp[j-w[i]]+v[i]) \]

核心程式碼如下:

for(int i = 0; i < N; i++){
    for(int j = w[i]; j <= W; j++){    // 這裡和0-1揹包不同
        dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
    }
}

注意內層for迴圈是從前向後更新dp陣列的,這是唯一和上面的0-1揹包問題區別的地方。原因在於,題目中每種物品的數量無限多,在放入一件物品 i 時,要考慮之前已經放過物品 i 的情況。

多重揹包

題目:有 N 種物品和容量為 W 的揹包。第 i 種物品的數量有n_i個,每個物品重量為 w_i,價值為 v_i,每種物品的數量無限。求將不超過揹包容量的物品裝入揹包能得到的最大價值。

特點:每種物品的數量不止一個,但有限。

基本的多重揹包問題狀態轉移方程如下:

\[dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]), 0\leq k \leq n[i] \]

核心程式碼如下:

for(int i = 0; i < N; i++){
    for(int j = W; j >= w[i]; j--){  
        for(int k = 1; k <= n[i]; k++){
            if(j < k * w[i])  break; 
            dp[j] = Math.max(dp[j], dp[j - w[i] * k] + v[i] * k);
        }
    }
}

一些揹包問題的題目

上面討論的三種情況只是最基本的揹包問題,實際刷題過程中會遇到這些基本問題的變體,例如需要揹包正好裝滿、求最小的物品件數、求裝包的方案數等等。這裡整理一些題目,後面遇到了持續更新~

0-1揹包類題目

考試策略
籃球隊 - 求方案數
牛妹的春遊 - 最小花費,多個條件,初始值限制
服務部署 - 多重條件

完全揹包類題目

換零錢 - 求總方案數
最少數量貨物裝箱問題 - 物品件數最小,恰好裝滿
拼湊面額 - 求方案數

多重揹包類題目

喬喬的包

相關文章