揹包問題

揹包問題

1. 算法分析

1.1 基礎模型

    01揹包特性的問題是把體積從大到小枚舉,徹底揹包特性的問題是把體積從小到大枚舉ios

1.2 時間複雜度

  • 01揹包: O(nm)
  • 徹底揹包: O(nm)
  • 多重揹包:
    樸素作法: O(nms)
    二制優化: O(nmlogs)
  • 單調隊列優化:
  • 混合揹包: O(nmlogs)
  • 二維費用的揹包問題: O(vnm)
  • 分組揹包: O(nms)
  • 有依賴的揹包問題: O(nm^2)
  • 揹包問題求方案數: O(nm)
  • 揹包問題求具體方案: O(nm)

1.3 處理技巧

揹包問題的兩種類型c++

1.3.1 求max/min

  1. 體積最可能是j: 初始化時f所有置爲0,且知足for(int j = m; j >= v; --j),即j 必需要 大於等於v才能跑
for(int j = m; j >= v; --j) f[j] = max(f[j], f[j - v] + w);
  1. 體積剛好是j: 初始化時f[0]=0, f[i]=正(負)無窮,且知足for(int j = m; j >= v; --j),即j 必需要 大於等於v才能跑
for(int j = m; j >= v; --j) f[j] = max(f[j], f[j - v] + w);
  1. 體積至少是j: 初始化時f[0]=0, f[i]=正(負)無窮,且知足for(int j = m; j >= 0; --j),即j 不須要 大於等於v才能跑
for(int j = m; j >= 0; --j)f[j] = max(f[j], f[max(0, j - v)] + w);

1.3.2 求count

    狀態轉移方程會對應地變成累加,初始化:f[0]=1,f[i]=0算法

2. 板子

2.1 01揹包問題

有 N 件物品和一個容量是 V 的揹包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大。輸出最大價值。數組

/* 思考:
1.爲何第二維循環是從大到小遍歷?
解答:計算是否放入第i個物品後的狀態是要基因而否放入第i-1個物品後的狀態來更新,而若是你從小到大遍歷,那麼是否放入第i個物品後的狀態變成基於已經放入或者沒放入第i個物品的狀態來更新,即一個物品可能被重複放入,從大到小是爲了保證這個物品只被放入一次。
而徹底揹包從小到大遍歷,是由於每一個物品的個數無限,你放入這個物品後能夠繼續放入物品,因此放入第i個物品後的狀態能夠基於已經放入第i個物品的狀態來更新。反之,若是你從大到小遍歷,那麼每一個物品只能放入一次,不符合題意。
*/

#include <bits/stdc++.h>

using namespace std;

int const N = 1e3 + 10;
int n, m;
int f[N];  // f[i]爲當體積爲i的狀況下的最大的價值

int main()
{
    cin >> n >> m;  // 輸入物品的個數和揹包的體積
    for (int i = 0; i < n; ++i)  // 從小到大循環個數
    {
        int vi, wi;  // 物品體積和價值
        cin >> vi >> wi;  // 讀入物品體積和價值
        for (int j = m; j >= vi; --j)  // 從大到小循環體積
            f[j] = max(f[j], f[j - vi] + wi);  // 在選和不選之中取最大
    }
    cout << f[m] << endl;
    return 0;
}

2.2 徹底揹包問題

有 N 種物品和一個容量是 V 的揹包,每種物品都有無限件可用。第 i 種物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大。輸出最大價值。數據結構

#include <bits/stdc++.h>

using namespace std;

int n, m;
int const N = 1010;
int f[N];  // f[i]表示體積爲i狀況下的最大價值

int main()
{
    cin >> n >> m;  // 輸入物品的個數和揹包的體積
    for (int i = 0; i < n; ++i)  // 從小到大循環個數
    {
        int vi, wi;  // 物品體積和價值
        cin >> vi >> wi;  // 讀入物品體積和價值
        for (int j = vi; j <= m; ++j)  // 從小到大循環體積
            f[j] = max(f[j], f[j - vi] + wi);  // 在選和不選之中取最大
    }
    cout << f[m] << endl;
    return 0;
}

2.3 多重揹包

有 N 種物品和一個容量是 V 的揹包。第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使物品體積總和不超過揹包容量,且價值總和最大。輸出最大價值。函數

2.3.1 樸素版本

#include <iostream>

using namespace std;

int const N = 1010;
int n, m;
int f[N];  // f[i]爲體積爲i的狀況下的最大價值

int main()
{
    cin >> n >> m;  // 物品個數和揹包的體積
    for (int i = 0; i < n; ++i)  // 從小到大枚舉物品
    {
        int v, w, s;  // 物品的體積、價值、個數
        cin >> v >> w >> s;  
        for (int j = m; j >= 0; j--)  // 從大到小枚舉體積,由於物品個數有限,全部這裏和01揹包相似
            for (int k = 1; k <= s && k * v <= j; ++k)  // 從1開始枚舉每一個物品的個數
                f[j] = max(f[j], f[j - k * v] + k * w);  // 在取k個之間選擇最大價值的
    }
    cout << f[m] << endl;  // 輸出體積爲m狀況下的最大價值
    return 0;
}

2.3.2 二進制優化

#include <iostream>
#include <vector>

using namespace std;

int n, m;
int const N = 2010;
int f[N];  // f[i]表示體積爲i時揹包的最大價值
struct Good  // 存儲新的生成的物品
{
    int v, w;
};

int main()
{
    
    // 二進制劃分
    vector <Good> goods;
    cin >> n >> m;  // 輸入物品種類和揹包體積
    for (int i = 0; i < n; ++i)
    {
        int v, w, s;  // 輸入每種物品的體積、價值和個數
        cin >> v >> w >> s;
        for (int k = 1; k <= s; k *= 2)  // 按照2進制的方式把這種物品劃分紅新的物品
        {                                // 即1份爲一個新的物品,2份爲一個新的物品,4份爲一個新的物品,8份爲一個新的物品
            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;  // 輸出體積爲m時的最大價值
    return 0;
}

2.4 混合揹包

有 N 種物品和一個容量是 V 的揹包。
物品一共有三類:優化

  • 第一類物品只能用1次(01揹包);
  • 第二類物品能夠用無限次(徹底揹包);
  • 第三類物品最多隻能用 si 次(多重揹包);

每種體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使物品體積總和不超過揹包容量,且價值總和最大。輸出最大價值。spa

#include <iostream>
#include <vector>

using namespace std;

int n, m ;
int const maxn = 1010;
int f[maxn];

struct Things
{
    int kind;
    int v, w;
};

int main()
{
    vector <Things> things;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
    {
        int v, w, s;
        cin >> v >> w >> s;
        if (s < 0) things.push_back({-1, v, w});  // 小於0爲01揹包
        else if (s == 0) things.push_back({0, v, w});  // 等於0爲徹底揹包
        else   // 大於0,那麼該物品就有對應的si個
        {
            for (int j = 1; j <= s; j *= 2)  // 使用二進制優化
            {
                s -= j;
                things.push_back({-1, j * v, j * w});
            }
            if (s > 0) things.push_back({-1, s * v, s * w});
        }
    }
    
    for (auto thing: things)
    {
        if (thing.kind < 0)  // 01揹包的話,從大到小
        {
            for (int j = m; j >= thing.v; --j) f[j] = max(f[j], f[j -thing.v] + thing.w);
        }
        else  // 徹底揹包的話,從小到大
        {
            for (int j = thing.v; j <= m; ++j) f[j] = max(f[j], f[j - thing.v] + thing.w);
        }
    }
    
    cout << f[m] << endl;
    return 0;
}

2.5 二維費用的揹包問題

有 N 件物品和一個容量是 V 的揹包,揹包能承受的最大重量是 M。每件物品只能用一次。體積是 vi,重量是 mi,價值是 wi。求解將哪些物品裝入揹包,可以使物品整體積不超過揹包容量,總重量不超過揹包可承受的最大重量,且價值總和最大。輸出最大價值。設計

#include <bits/stdc++.h>

using namespace std;

int n, v, m;
int const N = 1010;
int f[N][N];

int main()
{
    cin >> n >> v >> m;  // 輸入物品個數和揹包的承載體積和承載重量
    for (int i = 0; i < n; ++i)  // 從小到大枚舉物品
    {
        int vi, mi, wi;  // 每一個物品的體積、質量、價值
        cin >> vi >> mi >> wi;
        for (int j = v; j >= vi; --j)  // 體積作01揹包
            for (int k = m; k >= mi; --k)  // 重量作01揹包
                f[j][k] = max(f[j][k], f[j - vi][k - mi] + wi);  // 狀態轉移
    }
    cout << f[v][m] << endl;  // 輸出體積最多爲v,稱重量最大爲m時的揹包的最大價值
    return 0;
}

2.6 分組揹包問題

有 N 組物品和一個容量是 V 的揹包。每組物品有若干個,同一組內的物品最多隻能選一個。每件物品的體積是 vij,價值是 wij,其中 i 是組號,j 是組內編號。求解將哪些物品裝入揹包,可以使物品整體積不超過揹包容量,且總價值最大。輸出最大價值。code

#include <bits/stdc++.h>

using namespace std;

int const N = 110;
int f[N], v[N], w[N];// f[i]表示體積爲i時的最大價值,v存儲體積,w存儲價值
int n, m;

int main()
{
    cin >> n >> m; // 輸入分組個數和揹包的體積
    for (int i = 0; i < n; ++i)  // 從小到大枚舉分組
    {
        int s;  
        cin >> s;  // 讀入分組內的物品個數
        for (int j = 0; j < s; ++j) cin >> v[j] >> w[j];  // 讀入分組內的每一個物品的體積和價值
        for (int j = m; j >= 0; --j)  // 從大到小枚舉體積(01揹包)
            for (int k = 0; k < s; ++k)  // 枚舉每種分組內物品的狀況
                if (j >= v[k])  // 若是可以放入這個物品
                    f[j] = max(f[j], f[j - v[k]] + w[k]);  // 狀態轉移
    }
    cout << f[m] << endl;  // 輸出體積爲m的狀況下的最大價值
    return 0;
}

2.7 有依賴的揹包問題

有 N 個物品和一個容量是 V 的揹包。
物品之間具備依賴關係,且依賴關係組成一棵樹的形狀。若是選擇一個物品,則必須選擇它的父節點。
以下圖所示:
Image.png
若是選擇物品5,則必須選擇物品1和2。這是由於2是5的父節點,1是2的父節點。
每件物品的編號是 i,體積是 vi,價值是 wi,依賴的父節點編號是 pi。物品的下標範圍是 1…N。求解將哪些物品裝入揹包,可以使物品整體積不超過揹包容量,且總價值最大。輸出最大價值。

#include <bits/stdc++.h>

using namespace std;

int const N = 110;
int f[N][N], v[N], w[N];  // f[u][j]表示以u爲根節點,體積最大爲j的子樹的最大價值
int idx, e[N], ne[N], h[N];  // 創建鄰接表須要的數據結構
int n, m;  // 點數和體積

// 創建鄰接表
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

// dfs函數進行一次獲得以u結點爲根的樹的各類體積的最大價值
// 即可以獲得任何的f[u][j]
void dfs(int u)
{
    for (int i = h[u]; i != -1; i = ne[i])  // 遍歷與u相連的全部點
    {
        int son = e[i];  // 獲得與u相連的點爲son
        dfs(son);  // 對son進行dfs,獲得任意j的f[son][j]
        for (int j = m - v[u]; j >= 0; j--)  // 根結點u必須選上,因此最大能使用的體積爲m-v[u],枚舉體積
            for (int k = 0; k <= j; ++k)  // 枚舉以son結點爲根,任意體積k的子樹,選擇其中最大價值的一種
                f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);  // 狀態轉移
    }
    for (int i = m; i >= v[u]; --i) f[u][i] = f[u][i - v[u]] + w[u];  // 若是體積大於等於v[u]代表能放入根節點,那麼給全部這種的點補上以前沒有放入的根節點u
    for (int i = 0; i < v[u]; ++i) f[u][i] = 0;  // 若是體積小於v[u],那麼沒法放入根節點,那麼不可能出現這種狀況,則全爲0
}

int main()
{
    memset(h, -1, sizeof h);  // 鄰接表初始化
    int root;  // 標記根
    cin >> n >> m;  // 讀入點數和體積
    for (int i = 1; i <= n; ++i)  
    {
        int pre;  // 父節點
        cin >> v[i] >> w[i] >> pre;  // 讀入每一個物品的體積、價值和父節點
        if (pre == -1) root = i;  // 若是父節點爲-1,那麼爲根節點
        add(pre, i);  // 表示和父節點pre相連的點爲i
    }
    dfs(root);  // 從根開始dfs,樹通常都是從根開始dfs
    cout << f[root][m];  // 輸出以root爲根節點,體積最大爲m的最大價值
    return 0;
}

2.8 揹包問題求方案數

有 N 件物品和一個容量是 V 的揹包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大。輸出 最優選法的方案數。注意答案可能很大,請輸出答案模 109+7 的結果。

/*注意:
要保證揹包的體積所有用完,那麼f[i]初始化爲-INF,這樣才能保證每一個f[i]都是從f[0]轉移而來,保證揹包的體積才能所有用完;若是初始化爲f[i]全爲0,那麼揹包的體積可能不會所有用完*/

#include <bits/stdc++.h>

using namespace std;

int const N = 1e3 + 10, mod = 1e9 + 7, INF = -2e9;
int f[N], g[N];  // f[i]表示當體積恰好爲i時的最大價值,g[i]記錄當體積恰好爲j時的方案數
int n, m;

int main()
{
    cin >> n >> m;  // 輸入物品數目和揹包體積
    g[0] = 1; // 體積爲0的方案數爲1
    for (int i = 1; i <= m; ++i) f[i] = INF;  // 初始化時除了f[0]=0,其餘都爲負無窮,這樣保證全部的f[i]表示的爲當體積爲i時的最大價值,而不是體積最大爲i時的最大價值
    for (int i = 0; i < n; ++i)  // 讀入n個物品
    {
        int vi, wi;  
        cin >> vi >> wi;  // 讀入體積和價值
        for (int j = m; j >= vi; --j)  // 01揹包方式從大到小枚舉體積
        {
            int s = 0;  // 方案數
            int t = max (f[j], f[j - vi] + wi);  // 獲得較大的那個
            if (t == f[j]) s += g[j];  // 若是相等,那麼就要加上對應的方案數
            if (t == f[j - vi] + wi) s += g[j - vi];
            if (s > mod) s = s % mod;  // 超出數據要求範圍,取模
            g[j] = s;  // 記錄方案數
            f[j] = t;  // 記錄當價值爲j時的最大價值
        }
    }
    
    int maxi = INF;  // 記錄全部狀況的最大價值
    for (int i = 0; i <= m; ++i) maxi = max (maxi, f[i]);  // 獲得最大價值
    int res = 0;  // 記錄方案數
    for (int i = 0; i <= m; ++i)  // 循環每一種體積下的狀況
    {
        if (maxi == f[i])  // 若是當前體積對應的價值對應最大價值
        {
            res += g[i];  // 方案數就要加上這種狀況
            if (res > mod ) res %= mod;
        }
    }
    cout << res << endl;
    return 0;
}

2.9 揹包問題求具體方案

有 N 件物品和一個容量是 V 的揹包。每件物品只能使用一次。第 i 件物品的體積是 vi,價值是 wi。求解將哪些物品裝入揹包,可以使這些物品的整體積不超過揹包容量,且總價值最大。輸出 字典序最小的方案。這裏的字典序是指:所選物品的編號所構成的序列。物品的編號範圍是1…N。

// 若是f[i][vol] <- f[i-1][vol-vi]+wi,即f[i][vol]是從f[i-1][vol-vi]+wi轉移過來的,那麼說明第i個物品選擇了
// 若是f[i][vol] <- f[i-1][vol],即f[i][vol]是從f[i-1][vol]轉移過來的,那麼說明第i個物品沒選擇
// 若是是從f[i]轉移到f[i+1], 那麼打印路徑的時候要逆着打印,即從f[i+1]往f[i]打印,由於若是仍是正着打印,可能會使用到不是最優子結構內的數據

#include <bits/stdc++.h>

using namespace std;

int const N = 1e3 + 10;
int f[N][N], v[N], w[N];  // f[i][j]表示選擇i個物品體積爲j的最大價值
int n, m;  // 物品數目和體積

int main()
{
    cin >> n >> m;  // 讀入物品數目和揹包體積
    for (int i = 1; i <= n; ++i) cin >> v[i] >> w[i];  // 讀入每一個物品的體積和價值
    for (int i = n; i >= 1; --i)  // 要求字典序最小,因此從大到小枚舉,這樣打印路徑才能從小到大
        for (int j = m; j >= 0; --j)  // 二維的從大到小和從小到大均可以
        {
            f[i][j] = f[i + 1][j];  // f[i][j]必須有值且不能爲0,因此默認爲不選的狀況
            if (j >= v[i]) f[i][j] = max (f[i][j], f[i + 1][j - v[i]] + w[i]);  // 若是能夠選,那麼比較兩個狀況,取最大的價值狀況
        }
        
    int vol = m;  // 初始體積爲m
    for (int i = 1; i <= n; ++i)  // 從小到大枚舉,打印路徑
    {
        if (vol >= v[i] && f[i][vol] == f[i + 1][vol - v[i]] + w[i])  // 若是當前的剩餘體積vol大於當前物品v[i],且f[i][vol]這個狀態是從f[i+1][vol-v[i]+w[i]這個狀態轉移過來
        {                                                             // 那麼說明第i個物品選擇了
            cout << i << " ";
            vol -= v[i];
        }
    }
    return 0;
}

3. 典型例題

3.1 01揹包

acwing1020 潛水員
題意: 潛水員爲了潛水要使用特殊的裝備。他有一個帶2種氣體的氣缸:一個爲氧氣,一個爲氮氣。讓潛水員下潛的深度須要各類數量的氧和氮。潛水員有必定數量的氣缸。每一個氣缸都有重量和睦體容量。潛水員爲了完成他的工做須要特定數量的氧和氮。他完成工做所需氣缸的總重的最低限度的是多少?例如:潛水員有5個氣缸。每行三個數字爲:氧,氮的(升)量和睦缸的重量:

3 36 120
10 25 129
5 50 250
1 45 130
4 20 119

若是潛水員須要5升的氧和60升的氮則總重最小爲249(1,2或者4,5號氣缸)。你的任務就是計算潛水員爲了完成他的工做須要的氣缸的重量的最低值。
題解: 二維的01揹包,而後是最低值,初始化時須要f[0]=0,其餘爲0x3f,轉移時也須要注意。
代碼:

#include<bits/stdc++.h>

using namespace std;

const int N = 22, M = 80;

int n, m, K;
int f[N][M];

int main()
{
    cin >> n >> m >> K;

    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;

    while (K -- )
    {
        int v1, v2, w;
        cin >> v1 >> v2 >> w;
        for (int i = n; i >= 0; i -- )
            for (int j = m; j >= 0; j -- )
                f[i][j] = min(f[i][j], f[max(0, i - v1)][max(0, j - v2)] + w);
    }

    cout << f[n][m] << endl;
    return 0;
}

acwing278 數字組合
題意: 給定N個正整數A1,A2,…,AN,從中選出若干個數,使它們的和爲M,求有多少種選擇方案
題解:
f[0][0] = 1, f[0][i] = 0
f[i][j] = f[i - 1][j] + f[i - 1][j - vi]
=>
f[0] = 1;
f[i] = f[i] + f[i - vi]
代碼:

#include<bits/stdc++.h>
using namespace std;

int const N = 1e4 + 10;
int f[N];
int n, m;
int main()
{
    cin >> n >> m;
    f[0] = 1;
    for (int i = 0; i < n; ++i)
    {
        int v;
        cin >> v;
        for (int j = m; j >= v; --j)
            f[j] += f[j - v];
    }
    cout << f[m] << endl;
    return 0;
}

acwing734能量石
題意: 岩石怪物杜達生活在魔法森林中,他在午飯時收集了N塊能量石準備開吃。
因爲他的嘴很小,因此一次只能吃一塊能量石。能量石很硬,吃完須要花很多時間。
吃完第 i 塊能量石須要花費的時間爲Si秒。
杜達靠吃能量石來獲取能量。不一樣的能量石包含的能量可能不一樣。此外,能量石會隨着時間流逝逐漸失去能量。第 i 塊能量石最初包含Ei單位的能量,而且每秒將失去Li單位的能量。當杜達開始吃一塊能量石時,他就會當即得到該能量石所含的所有能量(不管實際吃完該石頭須要多少時間)。能量石中包含的能量最多下降至0。請問杜達吃完全部石頭能夠得到的最大能量是多少?
題解:
先i再i+1:Ans1 = Ei + Ei+1 - Si * Li+1
先i+1再i: Ans2 = Ei + Ei+1 - Si+1 * Li
所以,須要Ans1 > Ans2,則:Si * Li+1 < Si+1 * Li
即:Li/Si > Li+1/Si+1
那麼須要按照Li/Si從大到小排序才能保證能量最大(爲了防止精度損失,可使用乘法代替除法)
而後01揹包便可(本題的f[j]表示爲剛好的狀況,由於若是表示爲最多的狀況的話,每一個時刻能量石都在損失能量)
代碼:

#include<bits/stdc++.h>
using namespace std;

int const N = 1e6 + 10, M = 110;;
int f[N];
int n, m, t;
struct Stone
{
    int e, s, l;
}stone[M];
int kase;

bool cmp(struct Stone x, struct Stone y)
{
    return x.l * y.s > y.l * x.s;
}

int main()
{
    cin >> t;
    while (t--)
    {
        scanf("%d", &n);

        // 初始化
        m = 0;
        memset(f, 0xcf, sizeof f);
        f[0] = 0;
        memset(f, 0, sizeof f);
        memset(stone, 0, sizeof stone);

        // 讀入排序
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d %d %d", &stone[i].s, &stone[i].e, &stone[i].l);
            m += stone[i].s;
        }
        sort(stone + 1, stone + 1 + n, cmp);

        // 01揹包
        for (int i = 1; i <= n; ++i)
        {
            for (int j = m; j >= stone[i].s; --j)
            {
                f[j] = max(f[j], f[j - stone[i].s] + max(0, stone[i].e - stone[i].l * (j - stone[i].s)));
            }
        }

        int res = 0;
        for (int i = 0; i <= m; ++i) res = max(res, f[i]);
        printf("Case #%d: %d\n", ++kase, res);
    }
    return 0;
}

3.2 徹底揹包

acwing1023買書
題意: 小明手裏有n元錢所有用來買書,書的價格爲10元,20元,50元,100元。問小明有多少種買書方案?(每種書可購買多本)
題解:
f[i][j] = f[i - 1][j] + f[i - 1][j - v] + f[i - 1][j - 2v] + ... + f[i - 1][tv]
而f[i][j - v] = f[i -1][j - v] + f[i - 1][j - 2v] + ... + f[i - 1][j - tv]
因此
=>
f[i][j] = f[i - 1][j] + f[i][j - v] (這樣子就把三維優化爲二維了)
=>
f[j] = f[j - v] (這樣子就把二維優化爲一維了)
求方案,只有f[0]是有效的,其餘的都是無效的,無效的若是是加的類型,那就初始化爲0
代碼:

#include<bits/stdc++.h>

using namespace std;

int v[] = {0, 10, 20, 50, 100};
int n, m;
int const N = 1e3 + 10;
int f[N];
int main()
{
    cin >> m;
    memset(f, 0, sizeof f);
    f[0] = 1;
    for (int i = 1; i <= 4; ++i)
    {
        for (int j = 0; j <= m; ++j)
            if (j >= v[i]) f[j] += f[j - v[i]];
    }
    cout << f[m] << endl;
    return 0;
}

acwing532 貨幣系統
題意: 在網友的國度中共有 n 種不一樣面額的貨幣,第 i 種貨幣的面額爲 a[i],你能夠假設每一種貨幣都有無窮多張。爲了方便,咱們把貨幣種數爲 n、面額數組爲 a[1..n] 的貨幣系統記做 (n,a)。 在一個完善的貨幣系統中,每個非負整數的金額 x 都應該能夠被表示出,即對每個非負整數 x,都存在 n 個非負整數 t[i] 知足 a[i]× t[i] 的和爲 x。然而,在網友的國度中,貨幣系統多是不完善的,便可能存在金額 x 不能被該貨幣系統表示出。
例如在貨幣系統 n=3, a=[2,5,9] 中,金額 1,3 就沒法被表示出來。 
兩個貨幣系統 (n,a) 和 (m,b) 是等價的,當且僅當對於任意非負整數 x,它要麼都可以被兩個貨幣系統表出,要麼不能被其中任何一個表出。 
如今網友們打算簡化一下貨幣系統。
他們但願找到一個貨幣系統 (m,b),知足 (m,b) 與原來的貨幣系統 (n,a) 等價,且 m 儘量的小。
他們但願你來協助完成這個艱鉅的任務:找到最小的 m。
1≤n≤100,
1≤a[i]≤25000,
1≤T≤20
題解:
本題就是要求任何一個數字是否可以被其餘數字表示,若是可以被其餘數字表示,那麼這個數字就不選。所以能夠先把全部數字按照從大到小排序,而後利用徹底揹包判斷每一個數字是否能夠被其餘數字表示,即判斷經過前1~i-1個數字是否可以表示第i個數字,若是到達第i個數字的f[i]==0,那麼說明第i個數字不能被其餘數字表示。求全部a[i]對應的f[a[i]]時,能夠總體求一遍到f[a[n]],這樣在求出最大f[t]的同時,全部的f[i]都能求出來
代碼:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
int t, n, m;
int const N = 25010;
LL f[N];
int a[N];
int main()
{
    cin >> t;
    while (t--)
    {
        // 輸入、排序
        scanf("%d", &n);
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);

        // 求f[i]
        int ans = 0;
        int m = a[n];
        memset(f, 0, sizeof f);
        f[0] = 1;
        for (int i = 1; i <= n; ++i)
        {
            if (!f[a[i]]) ans++;
            for (int j = a[i]; j <= m; ++j)
                f[j] += f[j - a[i]];
        }
        printf("%d\n", ans);
    }   
    
    return 0;
}

3.3 分組揹包

acwing487金明的預算方案
題意: 金明今天很開心,家裏購置的新房就要領鑰匙了,新房裏有一間金明本身專用的很寬敞的房間。更讓他高興的是,媽媽昨天對他說:「你的房間須要購買哪些物品,怎麼佈置,你說了算,只要不超過N元錢就行」。今天一早,金明就開始作預算了,他把想買的物品分爲兩類:主件與附件,附件是從屬於某個主件的,下表就是一些主件與附件的例子:
Image 2.png
若是要買歸類爲附件的物品,必須先買該附件所屬的主件。每一個主件能夠有0個、1個或2個附件。附件再也不有從屬於本身的附件。
金明想買的東西不少,確定會超過媽媽限定的N元。因而,他把每件物品規定了一個重要度,分爲5等:用整數1~5表示,第5等最重要。他還從因特網上查到了每件物品的價格(都是10元的整數倍)。他但願在不超過N元(能夠等於N元)的前提下,使每件物品的價格與重要度的乘積的總和最大。設第j件物品的價格爲v[j],重要度爲w[j],共選中了k件物品,編號依次爲j1,j2,…,jk,則所求的總和爲:v[j1]∗w[j1]+v[j2]∗w[j2]+…+v[jk]∗w[jk](其中*爲乘號)
請你幫助金明設計一個知足要求的購物單。
題解:
本題屬於分組揹包模型
每一個物品若是有主件,那麼和在主鍵算。對於每一個分組,一共有2^i種,i爲附件的個數
在枚舉每種附件選配時可使用二進制來作分組揹包
代碼:

#include<bits/stdc++.h>

using namespace std;

typedef pair<int, int> PII;
int const N = 3e4 + 50;
int n, m;
PII master[N];
vector<PII> ser[N];
int f[N];

int main()
{
    cin >> m >> n;
    for (int i = 1; i <= n; ++i)
    {
        int v, priority, idx;
        cin >> v >> priority >> idx;
        if (!idx) master[i] = {v, priority * v};
        else ser[idx].push_back({v, v * priority});
    }

    for (int i = 1; i <= n; ++i)
        if (master[i].first)
        {
            for (int j = m; j >= 0; --j)
            {
                // 二進制選擇配件使用
                for (int k = 0; k < 1 << ser[i].size(); ++k)
                {
                    int v = master[i].first, w = master[i].second;
                    for (int p = 0; p < ser[i].size(); ++p)
                    {
                        if (k >> p & 1)
                        {
                            v += ser[i][p].first;
                            w += ser[i][p].second;
                        }
                    }
                    if (j >= v) f[j] = max(f[j], f[j - v] + w);
                }
            }
        }
    cout << f[m] << endl;
    return 0;
}

acwing1013機器分配
題意: 總公司擁有M臺相同的高效設備,準備分給下屬的N個分公司。各分公司若得到這些設備,能夠爲國家提供必定的盈利。盈利與分配的設備數量有關。問:如何分配這M臺設備才能使國家獲得的盈利最大?求出最大盈利值。分配原則:每一個公司有權得到任意數目的設備,但總檯數不超過設備數M。
1≤N≤10,1≤M≤15
題解: 分組揹包變形題,分組爲每一個公司得到多少臺設備。因爲M很是小,所以能夠分組揹包枚舉。
代碼:

#include<bits/stdc++.h>

using namespace std;

const int N = 11, M = 16;

int n, m;
int w[N][M];
int f[N][M];
int way[N];

int main()
{
    cin >> n >> m;

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

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

    cout << f[n][m] << endl;

    int j = m;
    for (int i = n; i; i -- )
        for (int k = 0; k <= j; k ++ )
            if (f[i][j] == f[i - 1][j - k] + w[i][k])
            {
                way[i] = k;
                j -= k;
                break;
            }

    for (int i = 1; i <= n; i ++ ) cout << i << ' ' << way[i] << endl;
    return 0;
}
相關文章
相關標籤/搜索