揹包九講

本篇文章設計的題目均在AcWing的2-12題。

一、01揹包

要求:每種物品只能選擇0個或1個,即對於每種物品只有選或者不選兩種情況。
題目描述:(題目鏈接)
\quadNN 件物品和一個容量是 VV 的揹包。每件物品只能使用一次。第 ii 件物品的體積是 viv_i,價值是 wiw_i。求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。

輸入格式
\quad第一行兩個整數,NVN,V,用空格隔開,分別表示物品數量和揹包容積。接下來有 NN 行,每行兩個整數 vi,wiv_i,w_i,用空格隔開,分別表示第 ii 件物品的體積和價值。

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

數據範圍
0<N,V10000<N,V≤1000
0<vi,wi10000<v_i,w_i≤1000

輸入樣例

4 5
1 2
2 4
3 4
4 5

輸出樣例

8

思路1:二維數組記錄
\quadf[i][j]表示只看前i物品,總體積是j的情況下總價值最大是多少。最大值答案就在f[n][0-V]中枚舉最大值即可,res=max(f[n][0-V])
\quad假設我們將前i-1個物品已計算完畢,考慮第i個物品,體積爲j時,第i個物品只有兩種選擇,即選或者不選。

  • 若不選,則相當於只考慮前i-1個物品且體積爲j的情況,這種情況下f[i][j]=f[i-1][j]
  • 若選,則相當於揹包容量只剩下j-v[i],這種情況下f[i][j]=f[i-1][j-v[i]]+w[i]

\quad最終結果就相當於在這兩種情況下取個最大值即可。還有個問題就是其初始化,很簡單,就是沒有物品給你選擇,揹包容量爲0時f[0][0]=0
\quad時間複雜度和空間複雜度都爲O(nV)O(nV)
程序(CPP)

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

const int N = 1010;
int f[N][N];
int v[N], w[N];

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

思路2:滾動數組優化爲一維
程序(CPP)

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

const int N = 1010;
int f[N];
int v[N], w[N];

int main()
{
    int n, m; 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;
}

二、完全揹包

要求:每種物品可以選0個或者無限個.
題目描述:(題目鏈接)
\quadNN 件物品和一個容量是 VV 的揹包,每種物品都有無限件可用。第 ii 件物品的體積是 viv_i,價值是 wiw_i。求解將哪些物品裝入揹包,可使這些物品的總體積不超過揹包容量,且總價值最大。輸出最大價值。

輸入格式
\quad第一行兩個整數,NVN,V,用空格隔開,分別表示物品數量和揹包容積。接下來有 NN 行,每行兩個整數 vi,wiv_i,w_i,用空格隔開,分別表示第 ii 件物品的體積和價值。

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

數據範圍
0<N,V10000<N,V≤1000
0<vi,wi10000<v_i,w_i≤1000

輸入樣例

4 5
1 2
2 4
3 4
4 5

輸出樣例

10

思路1
\quadf[i][j]表示只看前i物品,總體積是j的情況下總價值最大是多少。最大值答案就在f[n][0-V]中枚舉最大值即可,res=max(f[n][0-V])
\quad假設我們將前i-1個物品已計算完畢,考慮第i個物品,體積爲j時,第i個物品只有多種選擇,即選0個,1個,k個。我們可以再加上一重循環,枚舉每個物品能選的個數k,注意k的範圍爲(int k=0; k*v[i]<=j; k++)
程序(CPP):

#include <iostream>
using namespace std;

const int N = 1010;
int f[N][N], v[N], w[N];

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

\quad
思路2:利用滾動數組優化到一維,同01揹包優化思想

#include <iostream>
using namespace std;

const int N = 1010;
int f[N], v[N], w[N];

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

思路3
\quadf[i]表示總體積是i的情況下最大價值是多少,最終答案就是max(f[i]),其實也就是f[最大體積],因爲體積越大肯定能裝下物品的最大價值越高,至少不會降低。這裏面體積j從小到大枚舉表示f[i]可能從第i個物品轉移過來;從大到小枚舉的話表示只能從i-1個物品轉移過來。因此完全揹包相對於01揹包只需要體積從小到大枚舉。

程序(CPP)

#include <iostream>
using namespace std;

const int N = 1010;
int f[N], v[N], w[N];

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

Note:如果題目問的是恰好用了m的體積,問最大價值?這時候只需要初始化的時候除了f[0]=0外其他賦值爲-inf即可。

三、多重揹包

要求:每種物品能選擇的個數給個限制。
思路1:直接枚舉每種物品能選擇的個數k即可。

#include <iostream>
using namespace std;

const int N = 110;
int f[N][N];

int main()
{
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int v, w, num; cin >> v >> w >> num;
        for(int j = 0; j <= m; j++)
            for(int k = 0; k <= num && k*v <= j; k++)
                f[i][j] = max(f[i][j], f[i-1][j - k*v] + k*w);
    }
    
    int res = 0;
    for(int i = 0; i <= m; i++) res = max(res, f[n][i]);
    cout << res << endl;
    return 0;
}

思路2:滾動數組優化空間

#include <iostream>
using namespace std;

const int N = 110;
int f[N];

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

思路3:二進制優化
\quad假設有a個物品b,那麼我們將物品b拆分爲a份,每一份看作一個單獨的物品,這樣就將多重揹包轉化爲01揹包啦!但我們需要按照一份一份拆分嗎?其實是不必的,比如有7個物品b,那麼我們只需要拆分爲1個b,2個b,4個b,這樣我們能表示出任意一個物品b,假設我們選擇6個b,那麼就是4+2。即給定n個物品b,我們根據二進制拆分法能拆出來log(n)向上取整個數,這些數能表示出[0-n]中任意一個數。藉助於這個思想,我們就可以進行優化,假設有n個物品,揹包容量爲V,每種物品個數不超過num,則時間複雜度由暴力的O(nVnum)O(nV*num)變爲O(nVlog(num))O(nV*log(num))
\quad這裏還有個問題,如何找出log(n)log(n)向上取整個數使得這些數的組合能表示[0,n]中任意一個數。假設n=10n=10,我們取前三個數爲1,2,4,最後一個數不能是8,因爲是8的話就能表示出[0-15]間任意一個數,而每種物品個數不能超過10。那最後一個數怎麼選呢?其實這個數就是10-1-2-4=3,因爲1,2,4能表示出[0-7]間任意一個數,再拿出一個數3,與[0-7]間數相加,就可以得到[8-10]間任意一個數且不會超過10,因此1,2,4,3這4個數,就可以拼出任意[0-10]區間的數。
程序(CPP)

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

const int N = 2010;
int f[N];
struct Good
{
    int v, w;
};
int main()
{
    int n, m; cin >> n >> m;
    vector<Good> goods;
    for(int i = 1; i <= n; i++)
    {
        int v, w, s; cin >> v >> w >> s;
        // 二進制拆分物品
        for(int k = 1; k <= s; k *= 2)
        {
            s -= k;
            goods.push_back({v*k, w*k});
        }
        if(s>0) goods.push_back({v*s, w*s});
    }
    
    // 01揹包
    for(auto good: goods)
        for(int j = m; j >= good.v; j--)
            f[j] = max(f[j], f[j - good.v] + good.w);
    cout << f[m] << endl;
    return 0;
}

四、混合揹包問題

要求:每種物品的個數分爲多種情況。如下圖所示:
在這裏插入圖片描述在這裏插入圖片描述
\quad其實這個題跟多重揹包完全一樣,無非就是要設置下每種物品個數上限,最樸素的,直接按照多重揹包的不優化版寫出程序如下:

#include <iostream>
using namespace std;

const int N = 1010;
int f[N];

int main()
{
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int v, w, s; cin >> v >> w >> s;
        if(s==-1) s = 1;  // 設置物品數上限爲1
        else if(s==0) s = 1010;  // 設置物品無限個,不超過揹包體積
        for(int j = m; j >= v; j--)  //
            for(int k = 0; k <= s && k * v <= j; k++)
                f[j] = max(f[j], f[j - k * v] + k * w);
    } 
    
    cout << f[m] << endl;
    return 0;
}

\quad利用二進制優化才能過這個題,如下:

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

const int N = 1010;
int f[N];
struct Good
{
    int v, w;
};
int main()
{
    vector<Good> goods;
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        int v, w, s; cin >> v >> w >> s;
        if(s==-1) s = 1;
        else if(s==0) s = 1010;
        for(int k = 1; k <= s; k *= 2)
        {
            s -= k;
            goods.push_back({k * v, k * w});
        }
        if(s>0) goods.push_back({s * v, s * w});
    } 
    
    
    // 01揹包
    for(auto good: goods)
        for(int j = m; j >= good.v; j--)
            f[j] = max(f[j], f[j - good.v] + good.w);
    cout << f[m] << endl;
    return 0;
}

五、二維費用的揹包問題

要求:一維揹包問題可能只有一個限制,比如只有揹包容量的限制;二維的話有兩個限制,比如揹包容量和物品總重量限制。來個題就明白啦:
在這裏插入圖片描述
樣例

輸入:
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
輸出:
8

思路
\quadf[i][j]表示體積是i,重量是j時最大價值。第一重循環枚舉物品,第二重循環枚舉體積,第三重循環枚舉重量。因爲是01揹包,故而都是從大到小枚舉。程序如下:

#include <iostream>
using namespace std;

const int N = 1010;
int f[N][N];

int main()
{
    int n, V, M; cin >> n >> V >> M;
    for(int i = 1; i <= n; i++)
    {
        int v, m, w; // 體積、重量、價值
        cin >> v >> m >> w;
        for(int j = V; j >= v; j--)
            for(int k = M; k >= m; k--)
                f[j][k] = max(f[j][k], f[j - v][k - m] + w);
    }
    cout << f[V][M] << endl;
    return 0;
}

六、分組揹包問題

要求:把物品分成若干組,每組裏面最多選一件,來個題目:
在這裏插入圖片描述在這裏插入圖片描述
樣例

輸入:
3 5
2
1 2
2 4
1
3 4
1
4 5
輸出:
8

思路
\quadf[j]表示在j體積下的最大價值。與01揹包類似,第一重循環物品,第二重循環從大到小循環體積,然後依次把該組的每一個物品要麼放進去要麼不放進去。假設每一組有s個物品,則

for(int i = 1; i <= n; i++)
	for(int j = m; j >= v; j--)
		f[j] = max(f[j], f[j - v[0]]+w[0],..., f[j - v[s-1]]+w[s-1])

程序

#include <iostream>
using namespace std;

const int N = 110;
int f[N], v[N], w[N];

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

七、揹包問題求方案數

在這裏插入圖片描述

輸入樣例

4 5
1 2
2 4
3 4
4 6

輸出樣例

2

思路
\quad求方案數的時候就不能再像之前定義f[j]表示體積不超過j的情況下最大價值,而應該表示爲體積恰好爲j的情況下的最大價值,我們可以在初始化的時候將f[j]除了f[0]=0外其他全部初始化爲負無窮。同時還需要新增加一個數組g[j],表示體積恰好爲j的情況下得到最大價值的方案數。g[j]更新方案如下所述:

  • g[0]=1
  • f[j]=max(f[j], f[j-v[i]]+w[i]),當f[j]f[j]轉移而來時更新g[j]+=g[j],當其由f[j-v[i]]+w[i]轉移而來時更新g[j]+=g[j-v[i]],當二者相同時g[j]+=g[j]+g[j-v[i]]
  • 求揹包實現最大價值時的方案數,自然先求出揹包所裝物品的最大價值maxW,再求出用了多大的體積j實現了最大價值,最後答案就是這些實現最大價值的體積下對應的方案數之和
#include <iostream>
using namespace std;

const int N = 1010;
const int mod = 1e9+7, INF = 1e9;
int f[N]; // f[N]表示恰好體積是j的情況下最大價值
int g[N]; // g[N]表示體積是j的情況下最大方案數

int main()
{
    int n, m; cin >> n >> m;
    g[0] = 1;  // 初始方案數賦值
    for(int i = 1; i <= m; i++) f[i] = -INF;  // 賦值爲負無窮才能得到體積恰好爲j時的最大價值
    for(int i = 1; i <= n; i++)
    {
        int v, w; cin >> v >> w;
        for(int j = m; j >= v; j--)
        {
            int t = max(f[j], f[j-v]+w);
            int s = 0;  // 記錄當前體積下方案數
            if(t==f[j]) s += g[j];  // 是從體積j轉移而來
            if(t==f[j-v]+w)  s += g[j-v];  // 是從體積j-v轉移而來
            s = s % mod;
            f[j] = t, g[j] = s;
        }
    }
    int maxW = 0;  // 搜素最大價值
    for(int i = 0; i <= m; i++) maxW = max(maxW, f[i]);
    int res = 0;  // 記錄總的方案數
    for(int i = 0; i <= m; i++)
    {
        if(maxW == f[i])  // 當前體積下得到最大價值
        {
            res += g[i];
            res %= mod;
        }
    }
    cout << res << endl;
    return 0;
}

八、最優物品選擇方案

在這裏插入圖片描述
輸入樣例

4 5
1 2
2 4
3 4
4 6

輸出樣例

2

思路:見博客

九、有依賴的揹包問題

要求:選一件物品前必須選其依賴的物品

發佈了222 篇原創文章 · 獲贊 99 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章