【小结】单调队列优化 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;
}

浙公网安备 33010602011771号