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;
}
posted @ 2025-08-11 10:14  michaele  阅读(19)  评论(0)    收藏  举报