单调队列:基础
所谓单调对列,就是一个队列,其中元素是单调递增的或是单调递减的嘛。一般用来优化动态规划,因为决策总是取最优值,所以取出元素的时候只需要拿队首或队尾就行了,插入/删除元素的时候要从队首、队尾调整。均摊的效率是O(1),因为每个元素最多进出1次。
单调队列很经典的一个应用是最长不下降子序列的优化。具体的就不多说了,详细的分析网上到处是。
这里贴三道单调队列的基础题。第三题据说很经典。
RQ208
/* 题意大概是在环内求最大字段和 直接求的话难以处理环 于是DP+单调队列,题解来自RQ 用a[i]表示从第一个数到第i个数的累加和 那么题意就是求max(a[i]-min(a[j]))(n+1<=i<=2*n,i-n<=j<=i-1) 很容易就能想到 如果i<j且s[i]>s[j]的话 在之后转移的过程中s[i]是肯定不会用到的 2011-08-04 23:21 */ #include <stdio.h> #define MAXN 1000010 #define INF 19930317 int a[MAXN * 2]; int q[MAXN * 2]; int s[MAXN * 2]; int head, tail; int n, i, j, k, ans = -INF; int max(int a, int b) { return a > b ? a : b; } int sum(int l, int r) { return s[r] - s[l - 1]; } int main() { scanf("%d", &n); for (i = 1; i <= n; i++) { scanf("%d", &a[i]); a[i + n] = a[i]; } for (i = 1; i <= n * 2; i++) { if (i - q[head] == n) head++; s[i] = s[i-1] + a[i]; while (head <= tail && a[i] >= sum(q[tail], i)) tail--; q[++tail] = i; ans = max(ans, sum(q[head], i)); } printf("%d", ans); return 0; }
POJ3250
/* 题解来自http://hi.baidu.com/wzyjerry/blog/item/3b8c531eccaf39d1a6866926.html 题目大意:有 n 头牛头面向右站成一列。每头牛有一定的高度,并且能看到其前面高度比它低的牛的头顶,直到被某头高度大于等于它的高度的牛所挡住。计算每头牛能看到的牛头顶的数量的和。 解题方法:这个题的实质是求从队列中任意一个数开始连续下降的数的个数的和。所以需要知道从队列中的某个数开始有多少个连续的下降的数。所以考虑使用单调队列。由于这道题不是求一个区间的最值,所以不用记录队首,队列退化为栈。而且没有区间长度限制,所以不用记录下标,也不用保留原数据,降低了空间复杂度。时间复杂度为O(n),注意ans的范围。 单调队列代码量好小…… 2011-08-03 23:07 */ #include <stdio.h> #define MAXN 1000000 int stack[MAXN], top; int i, j, x, n; unsigned long ans; int main() { scanf("%d", &n); for (i = 1; i <= n; i++) { scanf("%d", &x); while (top > 0 && stack[top] <= x) top--; ans += top; stack[++top] = x; } printf("%u\n", ans); return 0; }
POJ2823
/* 一组数据,窗口大小为k,从左到右滑动,求每次窗口盖住的数字的最值 单调队列 题解:来自http://hi.baidu.com/wzyjerry/blog/item/3838f48af40e40cafd1f1089.html 题目大意:给你一个长度为n的数组,求从左到右的每个相邻的长度为k的区间内数的最大值和最小值。 解题方法:这道题数据较大(n最大为10^6)所以用纯模拟O(n*k)是铁定挂掉的,写的好的线段树可以卡时间过去。这里我们选择更为合适的单调队列解决。STL里有类似的优先队列。单调队列就是在一个队列中保证数据的单调性,可以很方便的求制定区间极值的一种队列。每个元素只会进队一次,所以复杂度为O(n),可以有效地降低时间复杂度。下面具体地说明一下单调队列。 1.数据结构:理论上来说应当是两个数组,一个存元素,一个存这个元素在原队列中的下标。实际上如果保留原队列的话,只需要记录下标即可。这样简化为只开一个长度为n的数组Q,减少了一半的空间开销。另外要记录队首s和队尾t。 2.初始化:将队首赋值为第一个元素。 3.维护过程(这里以建立单增队列求最大值为例):依次枚举剩下的N-1个元素,并且将当前未入队的第一个元素和队尾元素比较,当且仅当队列为非空并且队尾元素的值小于当前未入队的元素时,将队尾元素删除(也就是队尾指针-1),因为当前的元素比队尾元素大,所以在区间内队尾元素不会是最大值了。重复这个过程直到队列空或者队尾元素比当前元素大,一次插入结束。 4.由于单调队列是求指定长度区间的最值,所以需要控制区间长度。当插入结束后,需要用队尾元素下标和队首元素下标计算是否在一个区间内。如果不在,队首元素出队(队首指针+1)。重复这个过程直到控制到区间内,一次最大值寻找完毕。最大值出现在队首。 此题卡GCC…… 2011-08-03 22:53 */ #include <stdio.h> #define MAXN 1000100 int q[MAXN], head, tail; int a[MAXN]; int i, j, k, n, k; int main() { scanf("%d%d", &n, &k); for (i = 1; i <= n; i++) scanf("%d", &a[i]); q[0] = 1; for (i = 1; i <= n; i++) { while (head <= tail && a[q[tail]] >= a[i]) tail--; q[++tail] = i; while (q[tail] - q[head] + 1 > k) head++; if (i == k) printf("%d", a[q[head]]); else if (i > k) printf(" %d", a[q[head]]); } printf("\n"); head = tail = 0; q[0] = 1; for (i = 1; i <= n; i++) { while (head <= tail && a[q[tail]] <= a[i]) tail--; q[++tail] = i; while (q[tail] - q[head] + 1 > k) head++; if (i == k) printf("%d", a[q[head]]); else if (i > k) printf(" %d", a[q[head]]); } printf("\n"); return 0; }