揹包真的很簡單

作者 | 小 K

出品 | 公衆號:小 K 算法 (ID:xiaok365)

0****1

故事起源

有一個容量有限的揹包,容量爲 w,以及 m 個待選擇的物品,每個只有一件。每個物品有一定的重量和價值,那麼選擇哪些物品放入揹包,可使選擇的物品總價值最大呢?

02

問題解析

如果揹包沒有容量限制,那肯定是把所有的物品都放入揹包可使價值最大。

但現在揹包比較小,只能選擇部分裝進揹包,比如只能放一個,那就把鑽石裝進去。

很容易可以想到,儘量放重量小且單價高的物品,但怎麼對問題進行一個嚴謹的建模呢,繼續往下分析。

03

分析

揹包有一個固定的容量,容量是 1kg,或者 2kg,或者 3kg,其實具體的數量對問題的本質沒有影響。

對於物品來說,也就分兩種情況,要麼放入揹包,要麼不放。

有 m 個物品,那總共就有 2^m 種選擇方式,很明顯這個數量很大,所以也不可能直接把所有的選擇方式枚舉出來。

04

小問題過度大問題

假設揹包容量爲 1kg,那可裝入的最大價值就是將手錶裝入,其他的也裝不下。

如果有一個更大的揹包,它的容量可以看成是 2 個小容量的揹包的總和。

但它能裝入的價值卻不能簡單的直接分解爲 2 個小揹包,因爲物品只有一個,這會導致物品重複。

所以對物品也再進行一次劃分,m 個物品可以分解爲 m1+m2 個,同時揹包容量也分解爲 w1+w2。

再看上面左右兩邊,和原來的問題還是一樣的,本質不變,只是變成了數據規模更小的一個子問題。如果有了子問題的答案,那是不是就可以組合成更大規模的答案了呢?

我猜這裏肯定有同學會說,這樣分解的小問題一定能得到最終大問題的最優解嗎?我們來嘗試證明一下。

05

逆向思維

假設下面就是最終的最優解選擇的物品。

如果從某個位置砍一刀分開,保證 w1 和 w2 能裝下自己這邊的最終選擇物品,那最優解也就被分成了兩個小規模問題的最優解。這也說明如果枚舉了所有小規模最優解的組合方式,也一定能得到大規模的最優解。

06

算法建模

根據上面的分析,現在問題就變得簡單了,直接按物品和重量拆分小問題,通過小問題遞推出大問題就行了。

設 f[i][j] 表示前 i 個物品揹包容量爲 j 時,能選擇的最大價值。w[i] 表示第 i 個物品的重量,v[i] 表示第 i 個物品的價值。 

那爲什麼只需要從前 i-1 個物品遞推就行了呢,因爲只需要有一種情況能得到最優解就夠了,並不需要把前面的所有劃分都枚舉出來。這其實就相當於把 i 個物品劃分成 i-1 個物品和 1 個物品時的情況。前面的子問題也已經包含在當前的解中了。

代碼實現

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

07

總結

揹包在動態規劃中是一個非常重要的系列,涉及的類型和變種也非常的多,今天講的 01 揹包是最基本的一種,不過真正理解了 01 揹包,對後續其它的揹包也才能更好的掌握。

本文原創作者:小 K,一個思維獨特的寫手。
文章首發平臺:微信公衆號【小 K 算法】。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/4pLh6_mbbItljBPBfjkKPg