单调栈/队列

一、单调栈

定义

  单调栈,顾名思义就是说栈内的元素,按照某种方式排序下,必须是单调的。如果新入栈的元素破坏了单调性,就弹出栈内元素,知道满足单调性。它可以很方便地求出某个数的左边或者右边第一个比它大或者小的元素,而且总时间复杂度O(N)。

应用举例:poj-2559

题目大意:

  给出一个柱形统计图(histogram), 它的每个项目的宽度是1, 高度和具体问题有关。 现在编程求出在这个柱形图中的最大面积的长方形。

例如:

7 2 1 4 5 1 3 3

7表示柱形图有7个数据,分别是 2 1 4 5 1 3 3, 对应的柱形图如下,最后求出来的面积最大的图如右图所示。

思考:

  显而易见:最大矩形的高一定是某个柱形的高。那么枚举每个柱形为矩形的高度上限,令 L[x] 和 R[x] 分别代表柱形 x 的最左边和最右边第一个低于柱形x的柱形,那么答案就是 max{ h[i] * (R[i] - L[i] - 1) }。

  维护一个单调非递减栈:待处理第j个矩形

  loop:

    u := 栈顶元素;

    if(h[u] ≤ h[j]) break;

    R[u] = i;

    删除u;

   end;

  u := 栈顶元素;

  L[i] = u;

  将i入栈;

 

  为了简单起见,我们给输入的最后面分别添加一个0。

  最后遍历1~N求max{ h[i] * (R[i] - L[i] - 1) }即为答案。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #include<queue>
 6 #include<cstring>
 7 #include<stack>
 8 using namespace std;
 9 
10 typedef long long LL;
11 const int N = 100010;
12 stack<int> st;
13 int h[N], L[N], R[N];
14 
15 int main()
16 {
17     int i, j, k, m, n;
18     while(scanf("%d", &n) == 1 && n)
19     {
20         for(int i=1; i<=n; i++) scanf("%d", h+i);
21         h[n+1] = 0;
22         st.push(0);
23         for(i=1; i<=n+1; i++)
24         {
25             while(h[st.top()] > h[i]) {
26                 R[st.top()] = i;
27                 st.pop();
28             }
29             L[i] = st.top();
30             st.push(i);
31         }
32         LL ans = 0;
33         for(i=1; i<=n; i++)
34             ans = max(ans, 1LL*(R[i]-L[i]-1)*h[i]);
35         printf("%I64d
", ans);
36     }
37     return 0;
38 }
View Code

二、单调队列

  单调队列是这样一个队列:队内元素单调递增(递减),且队内后面元素的位置必须大于前面元素的位置(此处位置是指在原数列中的位置)。

问题:现在有一个数列{An},假设它有n项,问题是:求出每个区间长度为k的连续区间内数列的最小值。

1、入队时,从队尾向前扫描,直到找到队内某个元素小于它,把它放在这个元素的后面,将它作为队尾,即删除队内比它大的所有元素。

为什么可以这样删除呢?因为这个元素比所要删除的元素具有更优的性质。

何来更优?

首先,它入队比所要删除的元素晚,证明它的位置一定大于所要删除元素的位置,根据这一点,我们可以推出:

在以后的选择中,当所要删除元素的位置在所要找的区间时,它也一定在所要找的区间内。

而它又比所要删除元素小,所以它一定比所要删除元素更优。

所以它的入队意味着所要删除的元素已经失去了价值,故可以删除。

2、出队时,如果队首元素的位置不在所要找的区间内,那么就把下个元素作为队首,即删除原队列的队首。

值得一提的是,此时下个元素一定在所要找的区间内。

假设上次查找的区间为[a,a+k-1],那么经过上次出队后,队首元素的位置最坏情况下是a,那么它后面的元素的位置一定满足p>=a+1,

所以它后面的元素一定在这次要找的区间[a+1,a+k]中。

例题:poj-2823

题意:给定一个大小已知的数组以及一个大小已知的滑动窗口,窗口每个时刻向后移动一位,求出每个时刻窗口中数字的最大值和最小值。

代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<algorithm>
 5 #include<queue>
 6 #include<cstring>
 7 #include<stack>
 8 using namespace std;
 9 
10 typedef long long LL;
11 const int N = 1000100;
12 
13 void Scan(int &x)     //输入外挂
14 {
15     int res=0,ch,flag=0;
16     if((ch=getchar())=='-')
17         flag=1;
18     else if(ch>='0'&&ch<='9')
19         res=ch-'0';
20     while((ch=getchar())>='0'&&ch<='9')
21         res=res*10+ch-'0';
22     x = flag?-res:res;
23 }
24 void Out(int a)    //输出外挂
25 {
26     if(a < 0) putchar('-'), a= -a;
27     if(a>9)
28         Out(abs(a/10));
29     putchar(a%10+'0');
30 }
31 
32 int a[N];
33 int que[N];
34 int n, k;
35 
36 int main()
37 {
38     while(scanf("%d %d", &n, &k) == 2)
39     {
40         getchar();
41         for(int i=1; i<=n; i++) Scan(a[i]);
42         int headmn(0), headmx(0), tailmn(0), tailmx(0);
43         for(int i=1; i<=n; i++) {
44             while(headmn != tailmn && que[headmn] <= i-k)
45                 headmn ++;
46             while(headmn != tailmn && a[que[tailmn-1]] >= a[i])
47                 tailmn --;
48             que[tailmn++] = i;
49             if(i >= k) {
50                 Out(a[que[headmn]]);
51                 i == n ? putchar('
') : putchar(' ');
52             }
53         }
54         for(int i=1; i<=n; i++) {
55             while(headmx != tailmx && que[headmx] <= i-k)
56                 headmx ++;
57             while(headmx != tailmx && a[que[tailmx-1]] <= a[i])
58                 tailmx --;
59             que[tailmx++] = i;
60             if(i >= k) {
61                 Out(a[que[headmx]]);
62                 i == n ? putchar('
') : putchar(' ');
63             }
64         }
65     }
66     return 0;
67 }
View Code

小结练习

原文地址:https://www.cnblogs.com/WCB-ACM/p/5253967.html