单调队列优化DP 学习笔记

概述

单调队列优化DP是通过单调队列将转移方程中取最值的部分 \(O(1)\) 求出以优化复杂度。

转移方程通常是这样的:\(f_{i,j}=\max\{f_{i-1,k}+V\}+C\)\(f_{i,j}=\min\{f_{i-1,k}+V\}+C\)。其中 \(V\) 仅和 \(k\) 有关,即一个连续段的最值,若相邻状态的段的左右区间满足非降的关系,可以用单调队列维护 \(f_{i-1,k}+V\) 的值。

例题

P2627

反向考虑,从中选若干头牛不选,不选的效率和最小。设 \(f_i\) 表示第 \(i\) 头牛不选,前 \(i\) 头牛不选的效率和。有 \(f_i=\min_{j=i-K-1}^{i-1}\{f_j\}+E_i\),维护长度为 \(K+1\) 的区间中 \(f\) 的最小值。

#include<bits/stdc++.h>
using namespace std;
int n,m,bp,fp;
long long sum,w[1000005],q[1000005],f[1000005];
int main(){
  cin>>n>>m;
  for(int i=1;i<=n;i++)cin>>w[i],sum+=w[i];
  n++; 
  for(int i=1;i<=n;i++){
    while(q[bp]<i-m-1&&bp<=fp)bp++;
    f[i]=f[q[bp]]+w[i];
    while(fp>=bp&&f[q[fp]]>f[i])fp--;
    q[++fp]=i;
  }
  return cout<<sum-f[n]<<'\n',0;
}

一个小技巧:最后一个不选的奶牛的位置不确定,可以枚举 \([n-k,n]\),但可以增加虚点 \(n+1\),答案为 \(f_{n+1}\) 。刚开始的奶牛也找不出上一个选的位置,可以将 \(0\) 入队。

P1776

多重背包问题。

规定一下变量名:有 \(n\) 件物品,拿一件物品有 \(v_i\) 的价值,\(w_i\) 的花费,花费不超过 \(W\),每件物品有 \(num_i\) 件,求最大价值。

刚学 DP 时,有这样一个方程:\(f_{i,j}=\max_{k=0}^{\min(num_i,\lfloor\frac{j}{w_i}\rfloor)}\{f_{i-1,j-w_ik}+v_ik\}\)

观察可得,下标 \(j\) 每次减去 \(w_i\),因此转移只会在模 \(w_i\) 同余的每组内发生。分别枚举余数 \(b\),商 \(a\),代入得 \(f_{i,aw_i+b}=\max_{k=0}^{a}\{f_{i-1,(a-k)w_i+b}+v_ik\}\)

反向考虑 \(k\),把 \(k\) 的意义变成有几个物品不选,得到 \(f_{i,aw_i+b}=\max_{k=\max(0,a-num_i)}^{a}\{f_{i-1,kw_i+b}-v_ik\}+v_ia\)。这个式子满足单调队列优化的条件,维护 \(f_{i-1,kw_i+b}-v_ik\) 的最大值。

#include<bits/stdc++.h>
using namespace std;
int f[200005],q1[200005],q2[200005],n,W,v[200005],w[200005],num[200005],bp,fp;
int main()
{
  cin>>n>>W;
  for(int i=1;i<=n;i++)cin>>v[i]>>w[i]>>num[i];
  for(int i=1;i<=n;i++)
  {
    num[i]=min(W/w[i],num[i]);
    for(int b=0;b<w[i];b++)
    {
      bp=1,fp=0;
      for(int a=0,m;a<=(W-b)/w[i];a++)
      {
        m=f[a*w[i]+b]-a*v[i];
        while(bp<=fp&&q1[fp]<=m)fp--;
        q1[++fp]=m,q2[fp]=a;
        while(bp<=fp&&a-q2[bp]>num[i])bp++;
        f[a*w[i]+b]=max(f[a*w[i]+b],q1[bp]+a*v[i]);
      }
    }
  }
  return cout<<f[W]<<'\n',0;
}

[[动态规划]]

posted @ 2024-03-01 09:21  lgh_2009  阅读(8)  评论(0)    收藏  举报