多重背包模板题,数据极大需用单调队列优化,按体积余数分组,滑动窗口维护最优前驱状态,O(NV)。
OJ: luogu
题目 ID: U663797
难度:提高
标签:动态规划多重背包单调队列背包
创建: 2026-08-08 23:11
更新: 2026-10-07 12:15
前置题目
roj 1604「一本通 5.5 练习 3」理想的正方形A 教的『队列存下标、队首即窗口最值』被 B 直接用来在余数分组的等差窗口内 O(1) 取最优前驱算 dp[r+kv],只是先叠加了按体积取模分组与 g 备份。luogu P2627[USACO11OPEN] Mowing the Lawn GB 的 main.cpp 按体积余数分组后把多重背包转移整理成 kw + 窗口内 (g[r+jv]-jw) 最大值,正是 A 教的『转移化成常数项加窗口内 dp[j-1]-S[j] 最大值、用单调队列维护右移窗口取队首』这一步的直接套用,只是窗口从 1D 断点 j 搬到等差数列组内下标 k,并叠加余数分组、g 备份这两个 A 未教的新步骤。luogu P1725琪露诺B 的多重背包单调队列优化直接复用 A 教的滑窗单调队列三步(过期下标出队、弹掉队尾更差元素、用队头求最优),只是先把容量按体积余数分组成等差数列,使同一套队头转移能 O(1) 取每组窗口最优前驱。
后置题目
luogu P2569[SCOI2010] 股票交易B 的正解直接复用 A 教的这一步:A 把 dp[r+kv]=max(g[r+jv]-jw)+kw 的线性项提出 max 后,用单调队列维护递减的 g[r+jv]-jw 并取队首最优前驱;B 把同一手法套到 -j*AP_i+max(dp[pre][k]+k*AP_i)(卖出式同理),B 的 main.cpp 里弹出 q[head]<hold-as[day] 与 dp[pre][k]+k*ap[day] 的队尾淘汰和 A 的 main.cpp 三步完全同形,只是把容量下标换成持股数,额外叠加 A 未涉及的 pre=i-W-1 冷却期建模。luogu P6893[ICPC 2014 WF] Buffed BuffetA 教的按体积余数分组、把组内转移看成等差数列下标上的滑动窗口,正是 B 离散菜凹费用背包(main.cpp 中 for (int r = 0; r < cost; r++) 的逐余数分组单调队列)实际复用的一步;B 的主要新增难度在连续菜的分段二次最优分配与两类菜合并。luogu P2254[NOI2005] 瑰丽华尔兹B 的位移量 y-k 与 A 的 (k-j)w 一样把与窗口点相关的线性项提出 max,再对变换值 old_dp[x][k]-k 用单调队列取窗口最大值,只是从 1D 容量下标搬到网格行/列,并叠上整段压缩与遇家具清空队列luogu P2851[USACO06DEC] The Fewest Coins GB 的付款方 DP(main.cpp 的 build_pay_dp)里,按 mod v 的余数分组后用单调队列取窗口最值这一步与 A 教的完全相同(只把 A 的 max 价值版改成 min 最少张数版),B 另加的拆成两个 DP、找零完全背包和 T+Vmax^2 上界枚举 A 未涉及。
题意
有
思路
一句话本质:按体积取模分组后,每组内转移在等差数列下标上进行,形成滑动窗口最大值问题,用单调队列做到
二进制分组够快吗?
不够。每种物品拆出约
直接看转移方程,dependence 是什么样的?
对于第
这个依赖结构提示什么?
把
分完组后,转移变成什么形式?
令组内下标为
把
这个式子像什么?
对于
怎么
单调队列维护递减的
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* 多重背包问题 III — 单调队列优化
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 50005;
int n, V;
int dp[maxn], g[maxn];
int q[maxn], head, tail;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
cin >> n >> V;
for (int i = 1; i <= n; ++i) {
int v, w, s;
cin >> v >> w >> s;
memcpy(g, dp, sizeof(dp));
for (int r = 0; r < v; ++r) {
head = 0, tail = 0;
for (int k = 0; r + k * v <= V; ++k) {
int idx = r + k * v;
int val = g[idx] - k * w;
while (head < tail && q[head] < k - s) ++head;
while (head < tail && g[r + q[tail - 1] * v] - q[tail - 1] * w <= val) --tail;
q[tail++] = k;
dp[idx] = g[r + q[head] * v] + (k - q[head]) * w;
}
}
}
cout << dp[V] << '\n';
return 0;
}复杂度
- 时间:
。每组余数内每个位置入队出队各一次,每件物品均摊线性于 。 - 空间:
(dp 数组 + 备份数组 g + 单调队列)。
总结
多重背包从