单调队列优化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\)加入窗口之前进行计算

posted @ 2022-02-24 10:57  糖豆爸爸  阅读(155)  评论(0编辑  收藏  举报
Live2D