AcWing 2. 01揹包問題(模板)

題目鏈接:點擊這裏
在這裏插入圖片描述
在這裏插入圖片描述
01揹包問題描述:

NN 件物品和一個容量爲 VV 的揹包。放入第 ii 件物品耗費的費用是 CiC_i,得到的價值是 WiW_i。求解將哪些物品裝入揹包可使價值總和最大。

這是最基礎的揹包問題,特點是:每種物品僅有一件,可以選擇放或不放

用子問題定義狀態:即 F[i,v]F[i, v] 表示前 ii 件物品恰放入一個容量爲 vv 的揹包可以獲得的最大價值。

則其狀態轉移方程便是:F[i,v]=max{F[i1,v],F[i1,vCi]+Wi}F[i, v] = max\left\{F[i − 1, v], F[i − 1, v − C_i] + W_i\right\}

這個方程非常重要,基本上所有跟揹包相關的問題的方程都是由它衍生出來的。

所以有必要將它詳細解釋一下:“將前 ii 件物品放入容量爲 vv 的揹包中”這個子問題,若只考慮第 ii 件物品的策略(放或不放),那麼就可以轉化爲一個只和前 i1i − 1 件物品相關的問題。

  1. 如果不放第 ii 件物品,那麼問題就轉化爲“前 i1i − 1 件物品放入容量爲 vv 的揹包中”,價值爲 F[i1,v]F[i − 1, v]
  2. 如果放第 ii 件物品,那麼問題就轉化爲“前 i1i − 1 件物品放入剩下的容量爲 vCiv − C_i 的揹包中”,此時能獲得的最大價值就是 F[i1,vCi]F[i − 1, v − C_i] 再加上通過放入第 ii 件物品獲得的價值 WiW_i

注意到 F[i,v]F[i,v] 只與之前的狀態 F[i1,...]F[i-1, ...] 有關,所以可以枚舉 ii11NNvv00VV ,通過邊界 F[0,0...V]=0F[0, 0...V] = 0(即前 00 件物品放入任何容量的揹包中都只能獲得價值 00)就可以把整個 FF 數組遞推出來。

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;

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

以上方法的時間和空間複雜度均爲 O(VN)O(VN),其中時間複雜度應該已經不能再優化了,但空間複雜度卻可以優化到 O(V)O(V)

先考慮上面講的基本思路如何實現,肯定是有一個主循環 i1...Ni ← 1 . . . N,每次算出來二維數組 F[i,0...V]F[i, 0 . . . V] 的所有值。

那麼,如果只用一個數組 F[0...V]F[0 . . . V],能不能保證第 ii 次循環結束後 F[v]F[v] 中表示的就是我們定義的狀態 F[i,v]F[i, v] 呢?

F[i,v]F[i, v] 是由 F[i1,v]F[i − 1, v]F[i1,vCi]F[i − 1, v − C_i] 兩個子問題遞推而來,能否保證在推 F[i,v]F[i, v] 時(也即在第 ii 次主循環中推 F[v]F[v] 時)能夠取用 F[i1,v]F[i − 1, v]F[i1,vCi]F[i − 1, v − C_i] 的值呢?

事實上,這要求在每次主循環中我們以 vV...0v ← V . . . 0 的遞減順序計算 F[v]F[v],這樣才能保證計算 F[v]F[v]F[vCi]F[v − C_i] 保存的是狀態 F[i1,vCi]F[i − 1, v − C_i] 的值。

#include<iostream>
#include<algorithm>

using namespace std;
const int N = 1010;

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

【補充】

我們看到的求最優解的揹包問題題目中,事實上有兩種不太相同的問法。

有的題目要求“恰好裝滿揹包”時的最優解,有的題目則並沒有要求必須把揹包裝滿。

一種區別這兩種問法的實現方法是在初始化的時候有所不同。

  1. 如果是第一種問法,要求恰好裝滿揹包,那麼在初始化時除了 F[0]F[0]00,其它 F[1..V]F [1..V ] 均設爲 −∞,這樣就可以保證最終得到的 F[V]F [V ] 是一種恰好裝滿揹包的最優解。

  2. 如果並沒有要求必須把揹包裝滿,而是隻希望價格儘量大,初始化時應該將 F[0..V]F [0..V ] 全部設爲 00

這是爲什麼呢?

可以這樣理解:初始化的 FF 數組事實上就是在沒有任何物品可以放入揹包時的合法狀態。

  1. 如果要求揹包恰好裝滿,那麼此時只有容量爲 00 的揹包可以在什麼也不裝且價值爲 00 的情況下被“恰好裝滿”,其它容量的揹包均沒有合法的解,屬於未定義的狀態,應該被賦值爲 -∞ 了。

  2. 如果揹包並非必須被裝滿,那麼任何容量的揹包都有一個合法解“什麼都不裝”,這個解的價值爲 00,所以初始時狀態的值也就全部爲 00 了。

這個小技巧完全可以推廣到其它類型的揹包問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章