单调栈的定义:
元素顺序始终保持单调性(递增、递减)的栈。
(递增单调栈和递减单调栈的区别仅限于单调顺序不同,故以下为了方便讲解,默认提到的单调栈都为严格递增单调栈。)
维护方法:
压入新元素 (x) 时,将 (x) 与栈顶元素比较,若 (x) 小于等于栈顶元素,则将栈顶元素弹出,再与新的栈顶元素比较,直到 (x) 大于栈顶元素。
举例:
有一组数7,8,9,2,10,13,将这6个数从左往右压入严格递增单调栈:
1、压入7,此时栈为空,无栈顶元素,操作完成后栈内元素为:{7};
2、压入8,此时栈顶元素为7,7<8,故可以直接压入,操作完成后栈内元素为:{7、8};
3、压入9,此时栈顶元素为8,8<9,故可以直接压入,操作完成后栈内元素为:{7、8、9};
4、压入2,此时栈顶元素为9,9>2,故需要弹出栈顶元素9;同理,弹出8、7。此时栈为空,可以压入2,操作完成后栈内元素为:{2};
5、压入10,此时栈顶元素为2,2<10,故可以直接压入,操作完成后栈内元素为:{2、10};
6、压入13,此时栈顶元素为10,10<13,故可以直接压入,操作完成后栈内元素为:{2、10、13};
7、清空栈内元素;
使用单调栈时,一般是在弹出元素的过程中进行统计操作(即x小于等于栈顶元素的情况);
例题:
Largest Rectangle in a Histogram
题目大意:给定 (n) 个高为 (a_1、a_2、...、a_n) 的柱子,求该柱状图中,可勾勒出的最大的矩形面积。
思路:选择某个柱子 (i) ,将 (i) 向两边拓展,若碰到比 (a_i) 高的柱子就接着拓展,若碰到比 (a_i) 小的柱子就停止拓展。
直接暴力做的时间复杂度是 (O(n^2)) ,但可以使用单调栈优化。
考虑向一个递增单调栈中按 (i) 从小到大的顺序压入 (i) ,设栈顶元素为 (top) ,若 (a_i < a_top) ,则在弹出 (top) 的同时,将答案 (ans) 与 (top) 向左右拓展可得的最大面积取 (Max),并更新第 (i) 根柱子向左端能拓展到的最远处。
第 (i) 根柱子向最左端拓展的最远处可以在压入 (i) 之前弹出 (top) 的过程中更新,而最右端则应在弹出 (i) 的时候被更新。
代码:
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <float.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <limits.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
#include <wctype.h>
#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
#define int long long
using namespace std;
const int N = 8e4 + 5;
int n, ans, cnt, a[N], stck[N];
signed main () {
scanf ("%lld", &n);
for (int i = 1; i <= n; i ++)
scanf ("%lld", &a[i]);
for (int i = 1; i <= n; i ++) {
while (cnt && a[stck[cnt]] <= a[i]) {
ans += i - stck[cnt] - 1;
cnt --;
}
stck[++ cnt] = i;
}
for (int i = 1; i <= cnt; i ++)
ans += n - stck[i];
printf ("%lld
", ans);
return 0;
}