背包问题
对于背包问题这一类典型的动态规划问题,一般而言f[i]表示背包容量为i的时候能装入物体的最大价值
01背包
每个物品只能选择一次
int f[MAXN];
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++) {
for (int j = W; j >= w[i]; j--) {
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
完全背包
每个物品可以选择无限次
int f[MAXN];
memset(f, 0, sizeof(f));
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= W; j++) {
f[j] = max(f[j], f[j - w[i]] + v[i]);
}
}
多重背包
每一种物品只有 k i k_i ki个
二进制分组优化,把每一件物品拆分成多件,用01背包求解即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24V3tpnB-1690261757362)(image/经典动态规划/1690187935597.png)]
int index = 0;
for (int i = 1; i <= m; i++) {
int w, v, k;
cin >> w >> v >> k;
for (int c = 1; c <= k; c <<= 1) {
k -= c;
ww[index] = c * w;
vv[index++] = c * v;
}
if (k) {
ww[index] = k * w;
vv[index++] = k * v;
}
}
单调队列优化
for (int i = 1; i <= n; i++) {
memcpy(g, f, sizeof(f));
cin >> w >> v >> s;// 体积 价值 数量
for (int j = 0; j < w; j++) {// 优化枚举背包容量的这一维
// 根据模w的值不同拆分成w个类
int h = 0, t = -1;//代表队列的头和尾
for (int k = j; k <= m; k += v) {// m表示最大容量
if (h <= t && q[h] < k - s * w) h++; //维护单调队列
if (h <= t) f[k] = max(g[k], g[q[h]] + (k - q[h]) / w * v);// 更新
//当前值比队尾更有价值队尾则出队
while (h <= t && g[k] >= g[q[t]] + (k - q[t]) / w * v) t--;
q[++t] = k;
}
}
}
cout << f[m];
混合背包
也就是01,完全,多重背包的混合,也就是说有的物品只能选或不选,有的可以选任意多次,而有的有一定限制
for (循环物品种类) {
if (是 0 - 1 背包)
套用 0 - 1 背包代码;
else if (是完全背包)
套用完全背包代码;
else if (是多重背包)
套用多重背包代码;
}
二维费用背包
和01背包实际上基本一致,但是选一个物品会消耗两种价值
实际上,只需要在状态中增加一个维度用于存放第二种价值即可
for (int k = 1; k <= n; k++) // 仍然是先枚举物品
for (int i = m; i >= mi; i--) // 对经费进行一层枚举
for (int j = t; j >= ti; j--) // 对时间进行一层枚举
dp[i][j] = max(dp[i][j], dp[i - mi][j - ti] + 1);
分组背包
和01背包类似,但是在每一个组中,只能选择一件物品
解决方法:对每一个组进行一次01背包
存储方式:t[k][i]表示第k组第i件物品的编号,cnt[k]表示第k组有多少物品
for (int k = 1; k <= ts; k++) // 循环每一组(对应01中的选取物品)
for (int i = m; i >= 0; i--) // 循环背包容量(从大到小)
for (int j = 1; j <= cnt[k]; j++) // 循环该组的每一个物品(即决策哪一个要选)
if (i >= w[t[k][j]]) // 背包容量充足
dp[i] = max(dp[i], dp[i - w[t[k][j]]] + c[t[k][j]]); // 像0-1背包一样状态转移
有依赖的背包
分类讨论对于一个主件和它的若干个相关附件
买主件 + {附件的一个子集}
所以可以看作是一个分组背包,因为只有可能是以上诸多可能性中的一种
注: 对于多叉树而言,先要计算子节点集合再计算父节点集合
求背包问题的方案数
以01背包为例,对于其他背包也相似地
-
f
[
i
]
f[i]
f[i]表示最大价值的同时,用
c
[
i
]
c[i]
c[i]表示最优选法的方案数,
c
[
i
]
c[i]
c[i]初始化为1
f[i]表示背包容量为i的时候能装入物体的最大价值
cin >> n >> m;
for (int i = 0; i <= m; i++) {
c[i] = 1;
}
for (int i = 1; i <= n; i++) {
cin >> v >> w;
for (int j = m; j >= w; j--) {
if (f[j - w] + v > f[j]) {
f[j] = f[j - w] + v;
c[j] = c[j - v];
}
else if (f[j - w] + v == f[j]) {
c[j] = (c[j] + c[j - w]) % mod;
}
}
}
- 如果要求刚好装满背包的话,那么f数组的定义应该有所变化
f[i]表示恰好装满情况下的装入物体的最大价值,实际上,只需要修改初始化条件即可
for (int i = 1; i <= m; i++) f[i] = INT_MIN;
f[0] = 0, c[0] = 1;
背包问题求具体方案
注:此时还要求选取的为最小字典序的序列
此时f[i][j]表示i到n件序号的物品用于装j容量的背包所能装最大值
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++) {
p[i][j] = j;
f[i][j] = f[i + 1][j];
if (j - v[i] >= 0)
f[i][j] = max(f[i + 1][j], f[i + 1][j - v[i]] + w[i]);
if (j - v[i] >= 0 && f[i][j] == f[i + 1][j - v[i]] + w[i])
p[i][j] = j - v[i];
}
}
int j = m;
for (int i = 1; i <= n; i++) {
if (p[i][j] < j) {
cout << i << ' ';
j = p[i][j];
}
}