单调队列
单调队列,顾名思义,是一个队列且具有单调性!!(严格单调递增或递减,以下讨论单调递增,递减相同)
假设一组输入数据:3 2 1 4 5 6 3 4
执行过程:从队尾开始比较,若队尾元素大于等于当前元素,则删除队尾元素,队尾指针前移,并继续比较直到队尾元素小于当前元素,把当前元素加入队尾
则单调队列中的情况如下:
1)3
2)2 //因为2小于3,为了维护单调递增的性质,所以从队尾删掉3,并加入2,下同
3)1
4)1 4
5)1 4 5
6)1 4 5 6
7)1 3
8)1 3 4
最后队列里的数就是:1 3 4
单调队列本身概念很简单,性质也挺好理解的。以前却一直不知道怎么用。
最近看了一篇后缀数组的应用,里面讲用单调队列维护height数组的时候,提了一下单调队列,写得挺不错的。
原来要加一个“时间戳”,再加上双端队列的操作,才能让它更强大。
就拿poj的2823来说,其中的视窗操作正好对应了这个“时间戳”——数组下标。
如果当前元素的下标减去队首元素的下标大于视窗大小,则这个元素肯定在视窗外,应该被舍去,继续检查队首元素,直到队首元素在视窗以内。
这个“时间戳”概念真的挺重要的!
poj - 2823
1 #include <stdio.h>
2 #include <string.h>
3 #include <iostream>
4 using namespace std;
5
6 #define N 1000010
7
8 struct node{
9 int m, d;
10 }Inc[N], Dec[N];
11
12 int mi[N], mx[N], num, k;
13
14 bool cmpMAX(const int &a, const int &b){ return a>b; }
15 bool cmpMIN(const int &a, const int &b){ return a<b; }
16
17 void chk(int i, int &head, int &tail, node c[], bool cmp(const int &a, const int &b))
18 { //添加新节点到队尾,并删掉过期的头节点
19 while(tail >= head && cmp(c[tail].m, num)) tail--;
20 c[++tail].m = num;
21 c[tail].d = i;
22 while(cmpMAX(i-c[head].d+1, k)) head++;
23 }
24 void solve()
25 {
26 int n, i, j, id=0;
27 int head_I=0, tail_I=0, head_D=0, tail_D=0;
28
29 scanf("%d%d", &n, &k);
30 if(n>0 && k>=1) //初始化化队列里的第一个点
31 {
32 scanf("%d", &num);
33 Inc[0].m = Dec[0].m = num;
34 Inc[0].d = Dec[0].d = 0;
35 }
36 for(i=1; i<k; i++) //初始化视窗中的点: 加入队列
37 {
38 scanf("%d", &num);
39 chk(i, head_I, tail_I, Inc, cmpMIN);
40 chk(i, head_D, tail_D, Dec, cmpMAX);
41 }
42 mi[id] = Inc[head_I].m;
43 mx[id++] = Dec[head_D].m;
44 for(; i<n; i++) //添加并更新
45 {
46 scanf("%d", &num);
47 chk(i, head_I, tail_I, Inc, cmpMIN);
48 chk(i, head_D, tail_D, Dec, cmpMAX);
49 mi[id] = Inc[head_I].m;
50 mx[id++] = Dec[head_D].m;
51 }
52 printf("%d", mx[0]);
53 for(i=1; i<id; i++) printf(" %d", mx[i]);
54 printf("\n%d", mi[0]);
55 for(i=1; i<id; i++) printf(" %d", mi[i]);
56 printf("\n");
57 }
58
59 int main()
60 {
61 solve();
62 return 0;
63 }
(待补充)