好久没写blog了,前几天刷了一些基础题,感觉发现了很多并没有好好学会的东西(盲生!你发现了华点),于是打算更一波:首先是单调队列。
Q: 单调队列是什么?
A: 顾名思义,它是一种队列内元素单调递减或单调递增的队列(因为从队头和队尾都可以出队,所以大概是双端队列)。一般使用频率不高,但在有些程序中会有非同寻常的作用,例如可以用来优化DP之类。它的作用一般是高效地维护区间内最优解等,最优选择往往在队首。
Q: 这东西怎么搞啊qwq?
A: 一般的原理是:在处理 ans[i] 时,去除冗杂、多余的状态,使得每个状态在队列中只会出现一次;同时维护一个能瞬间得出最优解的队列,减少重新访问的时间;在取得自己所需的值后,为后续的求解做好准备。
一、单调队列的性质:
1、 队列中的元素其对应在原来的列表中的顺序必须是单调递增的。
2、 队列中所有元素的关系必须有序。
二、使用单调队列就涉及到去头和删尾,以区间最大值为例:
1、队列的头一定是在一段时间前就加入了队列,现在的队列头会不会离开了我们处理的区间呢?如果它离我们正在处理的 i 太远了,我们就要把它去掉,去除冗杂的信息。
2、为了保证队列的递减性,在从列队尾新插入元素 v 时,要考虑队列尾 p 处的值是否大于 v ,如果是,直接将 v 入队 ,此时队列递减性没有消失;如果不是,为了维护递减性,我们做如下考虑:v 是最新值,它的位置是目前最靠后的,它可成为以后的答案,必须留下;p - 1 的值与 v 大小不定,不能冒然删去它;而队列尾的值不但不是最大值,对于以后的情况又不如 v 优,因为 v 相比队列尾更靠后,而且值更大,所以删除队列尾,再同样判断 p - 1 处的值。
3、 这样,在维护好一个区间正确、严格递减的单调递减队列后,队列头就是当前区间的最大值了。借助这里的最优解,我们还可以由此得到目前所求的最优解(通常此处插入DP方程)。
Example_1:[Luogu 1886] 滑动窗口
维护区间最值问题,板子,放一个代码:
1 #include <cstdio> 2 #include <cctype> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn = 1000000 + 10; 7 int n, k, a[maxn], q[maxn], p[maxn], tail, head; 8 9 int main(int argc, const char *argv[]) 10 { 11 freopen("nanjolno.in", "r", stdin); 12 freopen("nanjolno.out", "w", stdout); 13 14 n = read(), k = read(); 15 for(int i = 1; i <= n; ++i) a[i] = read(); 16 17 head = 1, tail = 0; 18 for(int i = 1; i <= n; ++i) { 19 while( head <= tail && q[tail] >= a[i] ) --tail; 20 q[++tail] = a[i], p[tail] = i; 21 while( p[head] <= i - k ) ++head; 22 if( i >= k ) printf("%d ", q[head]); 23 } 24 printf("\n"); 25 26 head = 1, tail = 0; 27 for(int i = 1; i <= n; ++i) { 28 while( head <= tail && q[tail] <= a[i] ) --tail; 29 q[++tail] = a[i], p[tail] = i; 30 while( p[head] <= i - k ) ++head; 31 if( i >= k ) printf("%d ", q[head]); 32 } 33 printf("\n"); 34 35 fclose(stdin), fclose(stdout); 36 return 0; 37 }