zoukankan      html  css  js  c++  java
  • 背包DP全类型

    AcWing 2. 01背包问题

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 1010;
    
    int n, m;
    int v[N], w[N];
    int f[N];
    
    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;
    }
    

    AcWing 3. 完全背包问题

    状态表示:(f(i,j)) 其中的(i)表示只从前(i)种物品选,(j)表示选出来的物品总体积 (leq) j,因此(f(i,j))就表示从前(i)种物品中选出总体积不超过(j)的所有选法。
    状态计算:集合划分
    (i)种物品选(0)(1)(2)(3)(……)(k-1)(k)个。
    如果第(i)种物品,一个也不选,则说明我们只考虑前(i-1)种物品,即:(f(i-1, j))
    如果选第(i)种物品选(k)个,则我们可以采取与(01)背包类似的思路,去掉已经确定了价值的(k)个第(i)种物品,因为这并不会影响前(i-1)种物品我们选法的最大价值,因此得到方程:

    [f(i, j) = f(i-1, j - v[i] * k) + w[i] * k ]

    这里注意,第(i)种物品如果我们不选的话,就等价于(f(i-1, j)),因此不选的情况我们也可以并到上式子中,因此我们得到状态转移方程:

    [f(i, j) = max(f(i-1, j), f(i-1, j - v[i] * k) + w[i] * k) ]

    再进行优化:

    将式子展开:

    [f(i, j) = max(f(i-1, j), f(i-1, j-v) + w, f(i-1, j-2*v) + 2*w, f(i-1, j-3*v) + 3*w, ...) ]

    而:

    [f(i, j-v) = max( f(i-1, j - v) + f(i-1, j-2*v) + w, f(i-1, j-3*v) + 2*w, ...) ]

    观察上下两式,我们看出式子(1)(f(i-1, j-v) + w, f(i-1, j-2*v) + 2*w, f(i-1, j-3*v) + 3*w, ...)可以由式子(2):(f(i, j-v)) (+) (w)得到,于是我们就可以进行合并,我们可以得到这样的式子:

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

    因此这样就可以不用再枚举(k)了,只需要枚举两个状态即可。
    (f(i, j) = max(f(i-1, j), f(i, j-v) + w))(01)背包的状态转移方程并不同,(01)背包需要从(i-1)转移而来,而完全背包是从(i)转移而来。

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 1010;
    int n, m;
    int f[N], w[N], v[N];
    
    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 = v[i]; j <= m; j++) {
                f[j] = max(f[j], f[j - v[i]] + w[i]);
            }
        }
    
        cout << f[m] << endl;
        return 0;
    }
    

    AcWing 4. 多重背包问题 1

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 110;
    int n, m;
    int f[N][N];
    int v[N], w[N], s[N];
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) {
            cin >> v[i] >> w[i] >> s[i];
        }
        
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
                    f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
                }
            }
        }
        cout << f[n][m] << endl;
        return 0;
    }
    

    AcWing 5. 多重背包问题 II

    二进制优化

    这个如果再用朴素做法就不行了,(O(NVS)) 大约要(4*10^9),肯定不行,而若我们对(S)进行二进制拆开然后将整个问题当成(01)背包来做,那么复杂度就被有优化为了(O(NVlogS)) 大约为(2*10^7),优化了两个数量级。
    那为什么我们不用优化完全背包的方法来优化多重背包呢?先从状态转移方程出发

    [f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2*v]+2*w, ... ,f[i-1][j-s*v]+s*w) ag{1} ]

    而:

    [f[i][j-v] = max(f[i-1][j-v], f[i-1][j-2*v]+w, ... ,f[i-1][j-s*v]+(s-1)*w, f[i-1][j-(s+1)*v]+s*w) ag{2} ]

    可见(2)式比(1)式多了一项,因此不能这样优化。
    进入二进制优化正题
    假设有件物品的数量为(200),那么我们可以分成(1, 2, 4, 8, 16, 32, 64)这么几组,还剩下一个(73)不再拆分,分成的每一组我们最多只能拿一次那么就可以按照(01)背包做了。
    那会出现什么问题吗,例如我们想拿3件但是并没有啊,这问题并不会出现,(1 + 2 = 3)啊, 对于例子,(1,2)可以凑出(1-3)的任何一个数字,同理(1, 2)加一个4就可以凑出(1-7)任何一个数字,再同理,假设我们可以把(n)分出一组(1, 2, ..., 2^k, C)(C)不足以(2^{k+1}),否则我们就是(1, 2, ..., 2^k, 2^{k+1}),我们可以用(1, 2, ..., 2^k)凑出(2^{k+1} - 1)(根据等差数列求和)中的任何一个数字,那么再加一个(C)就可以凑出(1-n)中任何一个数。那么这样再做一遍(01)背包即可,复杂度就由(O(NVS))优化到了(O(NVlogS))

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 12000;
    int v[N], w[N];
    int n, m, f[N];
    
    int main() {
        cin >> n >> m;
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            int a, b, s;
            cin >> a >> b >> s;
            int k = 1;
            while (k <= s) {
                cnt++;
                v[cnt] = a * k, w[cnt] = b * k; //每个物品a 价值为b
                s -= k;
                k *= 2;
            }
            if (s > 0) {
                cnt++;
                v[cnt] = a * s, w[cnt] = b * s;
            }
        }
        n = cnt;
        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;
    }
    

    AcWing 6. 多重背包问题 III
    留个坑 等我学了单调队列DP 再来填。

    AcWing 7. 混合背包问题
    分开分析即可。

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 1010;
    int f[N];
    int n, m;
    
    int main() {
        cin >> n >> m;
        for (int i = 0; i < n; i++) {
            int v, w, s;
            cin >> v >> w >> s;
            if (s == 0) {
                for (int j = v; j <= m; j++) f[j] = max(f[j], f[j - v] + w);
            } else {
                if (s == -1) s = 1;
                for (int k = 1; k <= s; k *= 2) {
                    for (int j = m; j >= k * v; j--) {
                        f[j] = max(f[j], f[j - k * v] + k * w);
                    }
                    s -= k;
                }
                if (s) {
                    for (int j = m; j >= s * v; j--) {
                        f[j] = max(f[j], f[j - s * v] + s * w);
                    }
                }
            }
        }
        
        cout << f[m] << endl;
        
        return 0;
    }
    

    AcWing 8. 二维费用的背包问题
    在01背包基础上加了一维。

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 110;
    int n, V1, V2;
    int f[N][N];
    
    int main() {
        cin >> n >> V1 >> V2;
        for (int i = 1; i <= n; i++) {
            int v1, v2, w;
            cin >> v1 >> v2 >> w;
            for (int j = V1; j >= v1; j--) {
                for (int k = V2; k >= v2; k--) {
                    f[j][k] = max(f[j][k], f[j - v1][k - v2] + w);
                }
            }
        }
    
        cout << f[V1][V2] << endl;
    
        return 0;
    }
    

    AcWing 9. 分组背包问题

    #include <iostream>
    
    using namespace std;
    
    const int N = 110;
    int s[N], w[N][N], v[N][N];
    int n, m, f[N];
    
    int main() {
        cin >> n >> m;
        for (int i = 1; i <= n; i++) {
            cin >> s[i];
            for (int j = 0; j < s[i]; j++) {
                cin >> v[i][j] >> w[i][j];
            }
        }
    
        for (int i = 1; i <= n; i++) {
            for (int j = m; j >= 0; j--) {
                for (int k = 0; k < s[i]; k++) { //枚举有多少个 
                    if (v[i][k] <= j) {
                        f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
                    }
                }
            }
        }
        cout << f[m] << endl;
        return 0;
    }
    

    AcWing 10. 有依赖的背包问题

    #include <iostream>
    #include <algorithm>
    #include <cstring>
    
    using namespace std;
    
    const int N = 110;
    int f[N][N]; //表示从以u根的子树中选,总体积不超过j的最大价值
    int h[N], e[N], ne[N], idx;
    int n, m;
    int v[N], w[N];
    
    void add(int a, int b) {
        e[idx] = b, ne[idx] = h[a], h[a] = idx++;
    }
    
    void dfs(int u) {
        for (int i = h[u]; i != -1; i = ne[i]) {
            int son = e[i];
            dfs(e[i]);
            
            for (int j = m - v[u]; j >= 0; j--) { //左一遍分组背包
                for (int k = 0; k <= j; 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]
        for (int i = 0; i < v[u]; i++) f[u][i] = 0;
    }
    
    int main() {
        cin >> n >> m;
        int root;
        memset(h, -1, sizeof h);
        for (int i = 1; i <= n; i++) {
            int p;
            cin >> v[i] >> w[i] >> p;
            if (p == -1) root = i;
            else add(p, i);
        }
        
        dfs(root);
        
        cout << f[root][m] << endl;
        
        return 0;
    }
    

    AcWing 11. 背包问题求方案数
    类似于最短路的计数问题。

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 1010, Mod = 1e9 + 7;;
    int f[N], cnt[N];
    int n, m;
    
    int main() {
        cin >> n >> m;
        for (int i = 0; i <= m; i++) cnt[i] = 1;
        
        for (int i = 1; i <= n; i++) {
            int v, w;
            cin >> v >> w;
            for (int j = m; j >= v; j--) {
                int x = f[j - v] + w;
                if (x > f[j]) {
                    f[j] = x;
                    cnt[j] = cnt[j - v];
                } else if (x == f[j]) {
                    cnt[j] = (cnt[j - v] + cnt[j]) % Mod;
                }
            }
        }
        
        cout << cnt[m] << endl;
        
        return 0;
    }
    

    AcWing 12. 背包问题求具体方案
    对于方案的求解,我们可以类似于迷宫求最短路径时候的记录方案。
    就是判断一下每一步是从哪一步转移过去的,对于背包来说,f[i][j]可能由两个状态转移过来:

    1. 如果f[i][j] == f[i - 1][j]:表示从不选第i个物品转移到了f[i][j],那么记录转移状态的时候肯定必选第i个物品
    2. 如果f[i][j] == f[i - 1][j - v] + w:表示选第i个物品转移到了f[i][j],那么记录路径的时候肯定必然不能选第i个物品。
    3. 还有一种特例,f[i - 1][j] == f[i - 1][j - v] + w表示第i个物品可选可不选都可以转移到f[i][j],那么此是我们肯定是要选的,因为题目中还要保证字典序最小,所以选上肯定比不选的字典序要小。

    综上,能选必选。

    还有就是对于我们求转移状态的时候其实是倒着往前求得,类比于迷宫,而我们要获得字典序最小需要从前往后求,那么我们解决办法就是再求f的时候直接从ni枚举即可。

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 1010;
    
    int n, m;
    int f[N][N];
    int v[N], w[N];
    
    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 = 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 j = m; 
        for (int i = 1; i <= n; i++) {
            if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]) {
                cout << i << " ";
                j -= v[i];
            }
        }
        
        return 0;
    }
    

    关于记录方案还有另外一种方式:
    这种转移方法是来自迷宫求最短路的记录方案的灵感,这个题里y总只是提了一下,没有具体讲。

    开一个PII g[i][j]数组来记录,表示first表示(i, j)是由first这个点转移而来,second表示由first转移而来的时候第first物品是否被选.

    g[i][j] = {i + 1, false}:表示(i,j)这个状态是从没选第i + 1个物品转移而来。
    g[i][j] = {i + 1, true}:表示(i,j)这个状态是从选第i + 1个物品转移而来。

    然后边DP边记录即可。

    对于输出的过程:首先要判断一下g[i][j].second是否为true如果为true,那么表示是由选择一个物品转移而来,那么当前体积需要跟着变化,那就是减去当前的v,反之如果为false,那么就不必减去当前的v,还有注意这两种情况下i都需要进行转移。

    还要注意的就是求解的时候还是要倒序求,因为需要保证字典序最小,又因为这个WA了一发。

    int i = 1, j = m;
    while (i <= n && j) {
        if (g[i][j].second == true) {
            int t = i;
            cout << i << " ";
            i = g[i][j].first;
            j -= v[t];
        } else {
            i = g[i][j].first;
        }
    }
    

    详细的代码有一些细节都在注释里了:

    #include <iostream>
    #include <cstring>
    #include <algorithm>
    #include <vector> 
    
    using namespace std;
    
    typedef pair<int, bool> PII;
    const int N = 1010;
    
    int n, m;
    int f[N][N];
    int v[N], w[N];
    PII g[N][N]; //first表示第几件物品 second表示选没选
    vector<int> res;
    
    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 = 0; j <= m; j++) {
                f[i][j] = f[i + 1][j];
                g[i][j] = {i + 1, false}; 
                if (j >= v[i]) {
                    if (f[i + 1][j - v[i]] + w[i] >= f[i][j]) { //注意这里一定要大于等于,因为为了保证字典序,能选必选
                        g[i][j] = {i + 1, true};
                        f[i][j] = f[i + 1][j - v[i]] + w[i];    
                    }
                }
            }
        }
        
        int i = 1, j = m;
        while (i <= n && j) { //这里一定是&& 一开始写成了||,调了半个多小时
            if (g[i][j].second == true) {
                int t = i; //这里一定要临时存一下i,因为下边i接着变成了它的上一个状态,而j要减去的是当前状态的v
                cout << i << " ";
                i = g[i][j].first;
                j -= v[t];
            } else {
                i = g[i][j].first;
            }
        }
        
        return 0;
    }
    
  • 相关阅读:
    209. Minimum Size Subarray Sum
    208. Implement Trie (Prefix Tree)
    207. Course Schedule
    206. Reverse Linked List
    205. Isomorphic Strings
    204. Count Primes
    203. Remove Linked List Elements
    201. Bitwise AND of Numbers Range
    199. Binary Tree Right Side View
    ArcGIS API for JavaScript 4.2学习笔记[8] 2D与3D视图同步
  • 原文地址:https://www.cnblogs.com/ZhengLijie/p/14838926.html
Copyright © 2011-2022 走看看