动态规划——9大背包模型

背包模型

初始化总结

在这里插入图片描述

01背包求方案数

给定 N 个正整数 A1,A2,…,AN,从中选出若干个数,使它们的和为 M,求有多少种选择方案。

在这里插入图片描述

#include <iostream>

using namespace std;

const int N = 10010;

int f[N];

int main(void) {
    int n, m;
    cin >> n >> m;
    
    f[0] = 1;
    while (n--) {
        int v;
        cin >> v;
        for (int i = m; i >= v; --i)
            f[i] += f[i - v];
    }
    
    cout << f[m];
    return 0;
}

完全背包求方案数

给你一个n种面值的货币系统,求组成面值为m的货币有多少种方案。
n≤15,m≤3000
#include <iostream>

using namespace std;

const int N = 3010;

long long f[N];

int main(void) {
    int n, m;
    cin >> n >> m;
    
    f[0] = 1;
    while (n--) {
        int v;
        cin >> v;
        for (int i = v; i <= m; ++i) f[i] += f[i - v];
    }
    
    cout << f[m];
    return 0;
}

多重背包

#include <iostream>

using namespace std;

const int N = 6010;

int f[N];

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

多重背包II

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
#include <iostream>

using namespace std;

const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];

int main(void) {
    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;
            s -= k;
            k <<= 1;
        }

        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];

    return 0;
}

多重背包III

0<N≤1000
0<V≤20000
0<vi,wi,si≤20000
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 20010;

int n, m;
int f[N], g[N], q[N];

int main()
{
    cin >> n >> m;
    for (int i = 0; i < n; i ++ )
    {
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);
        for (int j = 0; j < v; j ++ )
        {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v)
            {
                if (hh <= tt && q[hh] < k - s * v) hh ++ ;
                while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
                q[ ++ tt] = k;
                f[k] = g[q[hh]] + (k - q[hh]) / v * w;
            }
        }
    }

    cout << f[m] << endl;

    return 0;
}

混合背包问题

加二进制优化

#include <iostream>

using namespace std;

const int N = 1010;

int f[N];

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

价值最小问题

状态定义:f[i][j]为体积至多为[i][j]的最小价值
#include <iostream>
#include <cstring>

using namespace std;

const int N = 110;

int f[N][N];

int main(void) {
    int m1, m2, n;
    cin >> m1 >> m2 >> n;
    
    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    
    while (n--) {
        int v1, v2, w;
        cin >> v1 >> v2 >> w;
        for (int i = m1; i >= 0; --i) {
            for (int j = m2; j >= 0; --j) {
                f[i][j] = min(f[i][j], f[max(0, i - v1)][max(0, j - v2)] + w);
            }
        }
    }
    
    cout << f[m1][m2];
    return 0;
}

分组背包

#include <iostream>

using namespace std;

const int N = 110;

int f[N], v[N][N], w[N][N], s[N];

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

分组背包求方案数

总公司拥有M台 相同 的高效设备,准备分给下属的N个分公司。

各分公司若获得这些设备,可以为国家提供一定的盈利。盈利与分配的设备数量有关。

问:如何分配这M台设备才能使国家得到的盈利最大?

求出最大盈利值。

分配原则:每个公司有权获得任意数目的设备,但总台数不超过设备数M。

输入格式
第一行有两个数,第一个数是分公司数N,第二个数是设备台数M;

接下来是一个N*M的矩阵,矩阵中的第 i 行第 j 列的整数表示第 i 个公司分配 j 台机器时的盈利。

输出格式
第一行输出最大盈利值;

接下N行,每行有2个数,即分公司编号和该分公司获得设备台数。

答案不唯一,输出任意合法方案即可。
#include <iostream>

using namespace std;

const int N = 20, M = 20;

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

int main(void) {
    int n, m;
    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 = m; j >= 0; --j) {
            f[i][j] = f[i - 1][j];
            for (int k = 1; k <= j; ++k) 
                f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
        }
    }
    
    int j = m;
    for (int i = n; i; --i) {
        for (int k = 0; k <= m; ++k) {
            if (k <= j && f[i][j] == f[i - 1][j - k] + w[i][k]) {
                way[i] = k;
                j -= k;
                break;
            }
        }
    }
    
    cout << f[n][m] << endl;
    for (int i = 1; i <= n; ++i) cout << i << ' ' << way[i] << ' ' << endl;
    
}

有依赖的背包问题

(分组背包)
有 N 个物品和一个容量是 V 的背包。
物品之间具有依赖关系,且依赖关系组成一棵树的形状。如果选择一个物品,则必须选择它的父节点。
每件物品的编号是 i,体积是 vi,价值是 wi,依赖的父节点编号是 pi。物品的下标范围是 1…N。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
#include <iostream>
#include <vector>

using namespace std;

const int N = 110;

int f[N][N], v[N], w[N];
vector<int> g[N];
int n, m;

void dfs(int u) {

    for (int ne : g[u]) { // 枚举物品
        dfs(ne);

        for (int i = m - v[u]; i >= 0; --i) { //  枚举体积,给w[u]留空间
            for (int j = 0; j <= i; ++j) { // 枚举方案,方案为体积
                f[u][i] = max(f[u][i], f[u][i - j] + f[ne][j]);
            }
        }
    }

    for (int i = m; i >= v[u]; --i) f[u][i] = f[u][i - v[u]] + w[u]; // 添加w[u]
    for (int i = 0; i < v[u]; ++i) f[u][i] = 0; // 不符合情况为0
}

int main(void) {
    cin >> n >> m;
    int root = -1;
    for (int i = 1; i <= n; ++i) {
        int a, b, p;
        cin >> v[i] >> w[i] >> p;
        if (p == -1) root = i;
        else g[p].push_back(i);
    }

    dfs(root);
    cout << f[root][m];
    return 0;
}

求最优方案数

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出 最优选法的方案数。注意答案可能很大,请输出答案模 109+7 的结果。
#include <iostream>
#include <cstring>

using namespace std;

const int N = 1010, MOD = 1e9 + 7;

int f[N], g[N];

int main(void) {
    int n, m;
    cin >> n >> m;
    
    memset(f, -0x3f, sizeof f);
    f[0] = 0; // 体积恰好为j的最大价值
    g[0] = 1;
    
    while (n--) {
        int v, w;
        cin >> v >> w;
        for (int i = m; i >= v; --i) {
            int val = max(f[i], f[i - v] + w);
            int cnt = 0;
            if (val == f[i]) cnt = g[i];
            if (val == f[i - v] + w) cnt = (cnt + g[i - v]) % MOD;
            g[i] = cnt;
            f[i] = val;
        }
    }
    
    int res = 0;
    for (int i = 0; i <= m; ++i) res = max(res, f[i]);
    
    int cnt = 0;
    for (int i = 0; i <= m; ++i) {
        if (res == f[i]) cnt = (cnt + g[i]) % MOD;
    }
    
    cout << cnt;
    
    return 0;
}

求具体方案

字典序最小
#include <iostream>

using namespace std;

const int N = 1010;

int f[N][N], v[N], w[N];

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

练习题

AcWing 4081. 选数:https://www.acwing.com/problem/content/4084/
在这里插入图片描述
题解
在这里插入图片描述

代码

// 转化为01费用背包问题
#include <algorithm>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 2005, M = 5010;

int v[N], w[N];
int f[N][M];
int main() {
    long long x, n, m;
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &x);
        while (x % 5 == 0) x /= 5, v[i]++;
        while (x % 2 == 0) x /= 2, w[i]++;
    }

    memset(f, -0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = m; j; j--) {
            for (int k = i * 25; k >= v[i]; k--)
                f[j][k] = max(f[j][k], f[j - 1][k - v[i]] + w[i]);
        }
    }
    int res = 0;
    for (int i = 1; i < M; i++) {
        res = max(res, min(i, f[m][i]));
    }
    cout << res << endl;

    return 0;
}

溪染的优惠券(贪心+01背包)
原题链接:溪染的优惠券
在这里插入图片描述
题解
本题是01背包的变形,在01背包中我们选择物品的顺序是没有影响的,就是如果这个物品在最终的结果里面,那么无论你早选晚选都在,但是此题不同,因为使用优惠卷有限制,如果你价格低了,最后的结果就不会包含它了,它早选后选的结果是不一样的。

怎么解决呢?
对优惠后的价格高到低排序,选优惠后价格也高的,这样不影响后面的优惠卷,也尽可能的选了优惠卷;之后就是01背包的转移。因为这里是找到最低的价格,我们只需要记住这个价格是否出现。然后从小到大找到最低的价格即可。时间复杂度O(nk)

n, k = (map(int, input().split()))
arr = []
for i in range(n):
    arr.append( tuple(map(int, input().split())) )
# 按优惠后的价格从高到低排序,这样不影响后面的优惠(我当时这里没想到啊)
arr.sort(key=lambda x: x[0] - x[1], reverse=True)
# 之后就是01背包,倒序更新状态
f = [0 for _ in range(10005)]
f[k] = 1
for i in range(n):
    for j in range(arr[i][0], k + 1):
        f[j-arr[i][1]] |= f[j]
res  = 1
while not f[res]:
    res+=1
print(res)
posted @ 2022-02-20 07:58  喝茶看狗叫  阅读(19)  评论(0)    收藏  举报