参考题目
AcWing 6. 多重背包问题 III
题目描述
有 N N N 种物品和一个容量是 V V V 的背包。
第 i i i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
思路
- 与二进制优化不同,使用单调队列优化是根据小于 v i v_i vi 的自然数作为余数来分类。
- 具体推理:
f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2v]+2w, ..., f[i-1][j-sv]+sw)
只需要从小到大,即使用f[i][j ~ j - sv](需要加一个偏移量)更新一下f[i][j]
f[i][j-v] = max(f[i-1][j-v], f[i-1][j-2v]+ w, ..., f[i-1][j-v-sv]+sw)
.
.
.
f[i][r+sv+v] = max(f[i-1][r+v], f[i-1][r+2v]+w, ..., f[i-1][r+sv+v]+sw)
f[i][r+sv] = max(f[i-1][r], f[i-1][r+v]+w, ..., f[i-1][r+sv]+sw)
.
.
.
f[i][r+v] = max(f[i-1][r], f[i-1][r+v]+w)
f[i][r] = max(f[i-1][r]);
- 可以发现需要维护一个 s i s_i si 的一个窗口的最大值,由于每个数还要加上一个偏移量,按下面方式处理就可以使用单调队列了。
构造一个简单情况
f[j] = max(f][j], f[j - v] + w, f[j - 2v] + 2w)
等价于 f[j] = max(f[j] - 2w, f[j - v] + w - 2w, f[j - 2v] + 2w - 2w) + 2w
等价于 f[j] = max(f[j] - 2w, f[j - v] - w, f[j - 2v]) + 2w
再简单一点
5 = max(1, 4 + 1, 2 + 2 )
5 = max(1 - 2, 4 + 1 - 2, 2 + 2 - 2) + 2
5 = max(-1, 3, 2) + 2
代码
时间复杂度: O ( N M ) O(NM) O(NM)
#include <iostream>
#include <cstring>
const int N = 20010;
int n, m;
int f[N], g[N], q[N];
int main()
{
std::cin >> n >> m;
for (int i = 0; i < n; i ++)
{
int v, w, s;
std::cin >> v >> w >> s;
memcpy(g, f, sizeof(f));
for (int j = 0; j < v; j ++ )
{
int hh = 0, tt = -1;
/*
以v = 2, s = 3, g[1] 与 g[3] 与 g[5] 与 f[7]为例
f[7] = max(g[5] + w, g[3] + 2w, g[1] + 3w);
*/
for (int k = j; k <= m; k += v)
{
if (hh <= tt && q[hh] < k - s * v) hh ++ ;
while (hh <= tt &&
g[k] >= g[q[tt]] + (k - q[tt]) / v * w) tt -- ;
q[++ tt] = k;
f[k] = g[q[hh]] + (k - q[hh]) / v * w;
}
}
}
std::cout << f[m] << '\n';
return 0;
}
参考资料
https://www.acwing.com/solution/content/1537/
https://www.acwing.com/solution/content/53507/
https://www.acwing.com/solution/content/6500/
https://www.acwing.com/solution/content/18483/
https://www.acwing.com/video/367/
https://www.bilibili.com/video/BV1354y1C7SF?share_source=copy_web