【小结】单调队列优化 DP

单调队列优化 DP 是一种常用的优化 DP 方式。可以通过它将时间复杂度降下来,从而保证不超时。

只要掌握了单调队列,难点就在于如何把 DP 转化成单调队列可维护的方式用单调队列维护。其它都和普通 DP 一样。

所以在思考题目的时候,只用管朴素 DP 怎么写,怎么转化成单调队列模板的形式即可。

P1776 宝物筛选

单调队列优化多重背包。

首先,二进制分解的做法在这题中也能过,这里不再赘述。

考虑朴素 DP。

\(f_{i,j}=f_{i-1,j-k\times w_i}+k\times v_i(1\leq k\leq \min(c_i,\lfloor \frac{m}{w_i}\rfloor))\)

复杂度:\(O(m\sum{c_i})\)。正常情况下会超时。

考虑单调队列优化。

把转移过来的 \(j\)\(j \mod w_i\) 分类。一共分成 \(w_i\) 类。

然后对于每一类余数为 \(d\) 的组有 \(\lfloor \frac{m-d}{w_i} \rfloor\) 个数。

只要把每一组做到 \(O(S)\),那么总的就能做到 \(O(nm)\)

对于一组数中的 \(x\)\(f_x=\max(f_k+(x-k)\times v_i)(x-c_i\leq k<x)\)

推式子。

\(f_x=\max(f_k-k\times v_i)+x\times v_i(x-c_i\leq k < x)\)

所以一段区间内仅关于 \(k\) 的最值,单调队列维护。

注意单调队列要维护两个参数。一个是 \(f_k-k\times v_i\),另一个是 \(k\)。务必不能用 \(k\) 来计算 \(f_k-k\times v_i\)。因为在更新之后 \(f_k\) 的值会改变。而我们要的是单次的转移,不能多次转移,否则会变成完全背包。

区间内选一个数相当于决策这个物品选多少个。

复杂度做到了惊人的 \(O(nm)\)。比二进制分解少带一个 \(log\)

#include<bits/stdc++.h>
using namespace std;
/*

*/
struct FSI{
	template<typename T>
	FSI& operator >> (T &res){
		res=0;T f=1;char ch=getchar();
		while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
		while (isdigit(ch)){res=res*10+ch-'0';ch=getchar();}
		res*=f;
		return *this;
	}
}scan;
typedef long long ll;
const int N=110,M=4e4+10;
int n,m,i,j,d,c[N],h,t;
ll w[N],v[N],f[M];
struct Node{
	ll x;
	int id;
};
Node q[M];
int main()
{
	scan>>n>>m;
	for (i=1;i<=n;i++) scan>>v[i]>>w[i]>>c[i];
	for (i=1;i<=n;i++)
	{
		for (d=0;d<w[i];d++)
		{
			h=1;
			t=0;
			for (j=0;j<=(m-d)/w[i];j++)
			{
				while (h<=t&&q[t].x<=f[j*w[i]+d]-j*v[i]) t--;
				while (h<=t&&j-q[h].id>c[i]) h++;
				q[++t]={f[j*w[i]+d]-j*v[i],j};
				f[j*w[i]+d]=max(f[j*w[i]+d],q[h].x+j*v[i]);
			}
		}
	}
	printf("%lld",f[m]);
	return 0;
}
posted @ 2026-08-21 16:48  GUO120822  阅读(1)  评论(0)    收藏  举报