5.4.2 单调队列优化多重背包
单调队列优化DP
优化多重背包
给定 \(N\) 种物品,每个物品有价值 \(v_i\) ,体积 \(w_i\) ,数量 \(c_i\)
有一个容量为 \(M\) 的背包,求最大价值
我们可以用二进制拆分法将每个物品拆分,时间复杂度为 \(O(M \sum \log c_i)\)
这里我们介绍单调队列优化,时间复杂度进一步优化为 \(O(NM)\)
在状态转移时,我们考虑选取第 \(i\) 个物品的个数 \(cnt\)
\[f[j] = \max_{1 \le cnt \le c_i} \{f[j - cnt * w_i] + cnt * v_i \}
\]
画出能够转移到 \(f[j]\) 的状态集合
当 \(j - 1\) 时
可以发现,这两个状态的决策候选集合没有重合,很难快速从 \(j - 1\) 转移到 \(j\) 对应的集合
但如果是 \(j\) 和 \(j - w_i\) ,这两者对应的集合有很大部分重合,所以我们可以用单调队列进行优化
将状态 \(j\) 按照除以 \(w_i\) 的余数进行分组,对每一组分别计算,不同组的状态在阶段 \(i\) 不会互相转移
例题
code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int f[N], g[N], q[N];
int main () {
scanf ("%d%d", &n, &m);
//枚举每种物品
for (int i = 1; i <= n; i++) {
int v, w, c;
scanf ("%d%d%d", &v, &w, &c);
memcpy (g, f, sizeof f);
//按照余数分组
for (int j = 0; j < w; j++) {
//队列的头尾
int h = 1, t = 0;
for (int k = j; k <= m; k += w) {
//排除队头过时元素
while (h <= t && q[h] < k - c * 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;
}
}
}
printf ("%d\n", f[m]);
return 0;
}