• 单调队列优化DP问题总结


    本节二刷用了五天的时间,真是相当于困在这里,这一节的思想很简单,但细节很多,不好理解(主要是因为笨),现把自己的领悟写一下,给后来者一些启示,水平有限,错误在所难免,勿喷!

    一、DP优化的步骤

    先不要考虑优化,先把\(DP\)模型搞清楚,写出基础代码,\(TLE\)后再考虑如何优化,用什么优先,这才是学习的正道,不能是上来就眉毛胡子一把抓,能学懂才是见了鬼了!本节六道题我全部采用了此策略,每篇题解中也是提供了基础的\(DP\)代码,然后再利用单调队列进行优化。不会走就想着跑,肯定不行,以后的学习中要注意这个策略。

    二、状态表示的确定

    这个东西,\(yxc\)大佬说主要靠经验,想想也是,如果我没有做过烽火传递这道题,天知道为什么要设置\(f[i]\)为前\(i-1\)个合法,第\(i\)个为选中状态的表示?
    而且这么表示后,还需要遍历一次尾巴上的\(i \sim i-m\)个才能找出答案,反正让我直接想我想不出来,学习了之后,再看后面的那道绿色通道,一下子就明白该怎么样进行状态定义了,这就是经验。经验靠什么来呢?当然是题量,经典题的题量,没有大量的经典题刷题,是不可能建立自己的知识图的,如果想学习,还想shuang jian,那是不可能学会的。

    三、单调队列之哨兵

    在单调队列中使用哨兵,主要是在第\(1\)个枚举到的数,它依赖的滑动窗口此时为空,无法获取到\(head\),那么此时的处理逻辑是什么样呢?

    一般来讲从两个方面来考虑:

    • 是不是窗口长度越界,需要出队首元素?
    while (hh <= tt && q[hh] < i - m) hh++; 
    

    这种情况下,由于是第\(1\)个枚举到的数,肯定不可能窗口长度越界,如果按上面的写法,hh=0 tt=-1,此时hh>tt,所以while不执行,直接短路运算,没机会讨论q[hh]是否小于i-m,这里是安全的。

    但有的写法,这里是这样的

    if(q[hh]< i-m) h++;  
    
    

    此时,没有了hh<=tt的保护,这里队列中还没有元素,但是却访问q[hh]是否小于i-m,这就是在物理意义上有问题了!此处我的建议是不管有用没用,统一使用:

    while (hh <= tt && q[hh] < i - m) hh++; 
    
    

    这么写没毛病,别手懒!

    • 使用队列头
    res = max(res, s[i] - s[q[hh]]);
    
    

    此时如果没有哨兵,同样存在着无法直接访问使用q[hh]的问题,造成代码逻辑上出错

    在使用哨兵前,我们要思考如果要虚拟出一个哨兵,它要解决什么问题?

    (1)队列天生不是空的,随时可以取队列头

    (2)第\(1\)个数字的运算逻辑和其它数字没有区别,不用特殊判断

    总结
    (1)增加哨兵能简单的解决边界问题
    (2)依赖于前序关系的需要加哨兵,不依赖前序关系的可以加哨兵,也可以不加哨兵
    (3)如果想要加哨兵,需要遵照下面的原则

    如果是逆时针,正序遍历,那么添加的哨兵应该是第\(0\)个,\(q[++tt]=0\)
    如果是顺时针,倒序遍历,那么添加的哨兵应该是第\(n+1\)个,\(q[++tt]=n+1\)

    AcWing 154. 滑动窗口

      int hh = 0, tt = -1;
      q[++tt]=0;
      
      for (int i = 1; i <= n; i++) {
          while (hh <= tt && i + 1 - k > q[hh]) hh++;
          while (hh <= tt && a[q[tt]] >= a[i]) tt--;
          q[++tt] = i;
          if (i >= k) printf("%d ", a[q[hh]]);
      }
    

    此题,枚举到的\(i\)号元素,不依赖于前序进行推导计算,直接输出前序即可,加不加哨兵都是一样的。

    AcWing 135. 最大子序和

       int hh = 0, tt = -1;
       q[++tt] = 0;    //放入哨兵结点
       int res = -INF; //预求最大,先设最小
       for (int i = 1; i <= n; i++) {
           while (hh <= tt && q[hh] < i - m) hh++;
           res = max(res, s[i] - s[q[hh]]);
           while (hh <= tt && s[q[tt]] >= s[i]) tt--;
           q[++tt] = i;
       }
    

    此题,枚举到的\(i\)号元素,依赖前序进行推导计算需要加哨兵,并且窗口中不包含\(i\),在\(i\)加入窗口之前进行计算

    AcWing 1087. 修剪草坪

    int hh = 0, tt = -1;
    q[++tt] = 0;
      for (int i = 1; i <= n; i++) {
          while (hh <= tt && i - q[hh] > m) hh++;
          f[i] = max(f[i - 1], f[max(0, q[hh] - 1)] + s[i] - s[q[hh]]);
          while (hh <= tt && f[i - 1] - s[i] >= f[max(0, q[tt] - 1)] - s[q[tt]]) tt--;
          q[++tt] = i;
      }
    

    此题,枚举到的\(i\)号元素,依赖前序进行推导计算需要加哨兵,并且窗口中不包含\(i\),在\(i\)加入窗口之前进行计算

  • 相关阅读:
    Nginx Record
    Go 查找元素
    博客转移公告
    模板库
    模板库
    【BZOJ2276】Temperature
    【BZOJ3524】Couriers
    【BZOJ4458】GTY的OJ
    AtCoder Grand Contest 007
    Editing 2011-2012 ACM-ICPC Northeastern European Regional Contest (NEERC 11)
  • 原文地址:https://www.cnblogs.com/littlehb/p/15930765.html
Copyright © 2020-2023  润新知