动态规划——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)
本文来自博客园,作者:喝茶看狗叫,转载请注明原文链接:https://www.cnblogs.com/zdwzdwzdw/p/15914630.html