单调队列小结
好久没写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 }
Example_2:[HAOI 2007] 理想的正方形
很有趣的一道题,二维的滑动窗口233。思路是先对原矩阵每行做维护,再对得到的新矩阵的每一列做维护,得到题意的最优解。
1 #include <queue> 2 #include <cstdio> 3 #include <cctype> 4 #include <cstring> 5 #include <iostream> 6 #include <algorithm> 7 using namespace std; 8 9 typedef pair<int, int> pairs; 10 11 const int maxn = 1000 + 10; 12 int n, m, c, mut[maxn][maxn], max_tmp[maxn][maxn], min_tmp[maxn][maxn]; 13 int fin_max[maxn][maxn], fin_min[maxn][maxn], q[maxn], p[maxn], head, tail; 14 15 inline int read() { 16 register char ch = 0; register int w = 0, x = 0; 17 while( !isdigit(ch) ) w |= (ch == '-'), ch = getchar(); 18 while( isdigit(ch) ) x = (x * 10) + (ch ^ 48), ch = getchar(); 19 return w ? -x : x; 20 } 21 22 int main(int argc, char const *argv[]) 23 { 24 freopen("nanjolno.in", "r", stdin); 25 freopen("nanjolno.out", "w", stdout); 26 27 int ans = 2147483647; 28 scanf("%d%d%d", &n, &m, &c); 29 for(int i = 1; i <= n; ++i) for(int j = 1; j <= m; ++j) mut[i][j] = read(); 30 31 for(int i = 1; i <= n; ++i) { 32 head = 1, tail = 0; 33 for(int j = 1; j <= m; ++j) { 34 while( tail - head + 1 && q[tail] >= mut[i][j] ) --tail; 35 q[++tail] = mut[i][j], p[tail] = j; 36 while( p[head] <= j - c ) ++head; 37 if( j >= c ) min_tmp[i][j] = q[head]; 38 } 39 } 40 for(int i = 1; i <= n; ++i) { 41 head = 1, tail = 0; 42 for(int j = 1; j <= m; ++j) { 43 while( tail - head + 1 && q[tail] <= mut[i][j] ) --tail; 44 q[++tail] = mut[i][j], p[tail] = j; 45 while( p[head] <= j - c ) ++head; 46 if( j >= c ) max_tmp[i][j] = q[head]; 47 } 48 } 49 for(int j = 1; j <= m; ++j) { 50 head = 1, tail = 0; 51 for(int i = 1; i <= n; ++i) { 52 while( tail - head + 1 && q[tail] >= min_tmp[i][j] ) --tail; 53 q[++tail] = min_tmp[i][j], p[tail] = i; 54 while( p[head] <= i - c ) ++head; 55 if( i >= c ) fin_min[i][j] = q[head]; 56 } 57 } 58 for(int j = 1; j <= m; ++j) { 59 head = 1, tail = 0; 60 for(int i = 1; i <= n; ++i) { 61 while( tail - head + 1 && q[tail] <= max_tmp[i][j] ) --tail; 62 q[++tail] = max_tmp[i][j], p[tail] = i; 63 while( p[head] <= i - c ) ++head; 64 if( i >= c ) fin_max[i][j] = q[head]; 65 } 66 } 67 68 for(int i = c; i <= n; ++i) for(int j = c; j <= m; ++j) 69 ans = min(ans, fin_max[i][j] - fin_min[i][j]); 70 printf("%d\n", ans); 71 72 fclose(stdin), fclose(stdout); 73 return 0; 74 }