摘自https://blog.csdn.net/wubaizhe/article/details/70136174
1、定义
单调栈是一种特殊的栈,其栈内的元素都保持一个单调性(单调递增或者递减)。
- 单调递增栈,从栈底到栈顶依次递增(单调非递减栈:允许有相等)
- 单调递减栈,从栈底到栈顶依次递减(单调非递增栈:允许有相等)
假设下图是一个栈内元素的排列情况(单调递增的栈):
2、作用
利用单调栈,可以找到从左(或者右)遍历第一个比它小(或者大)的元素的位置。
3、算法过程
假设有一个单调递增的栈 S和一组数列: a = { 5 3 7 4}
用数组L[i]
表示 第i个数向左遍历的第一个比它小的元素的位置
如何求L[i]?
3.1 朴素的算法 O(n^2)
可以按顺序枚举每一个数,然后再依此向左遍历。 但是当数列单调递减时,复杂度是严格的O(n^2)。
3.2 单调栈 O(n)
我们按顺序遍历数组(i : 1 -> n),然后构造一个单调递增栈。栈中存放的是元素下标,而非元素本身。
{5 3 7 4}
(1)i = 1时,因为栈为空,L[1] = 0,此时再将第一个元素的位置下标1存入栈中。
此时栈中情况:
(2)i = 2时,因当前元素a[i] = 3小于栈顶元素下标1对应的元素a[1] = 5,故将下标1弹出栈, 此时栈为空 ,故L[2] = 0 。然后将元素3对应的位置下标2存入栈中。
此时栈中情况:
(3)i = 3时,因当前元素a[i] = 7大于栈顶元素下标2对应的元素a[2] = 3,故
L[3] = S.top() = 2 (栈顶元素的值,说明第一个比它小的元素的下标为多少),然后将元素7对应的下标3存入栈 。
此时栈中情况:
(4)i = 4时,因当前元素a[i] =4小于栈顶元素下标3对应的元素a[3] = 7,为保持单调递增的性质,应将栈顶元素下标3弹出 ,而当前元素a[i] =4大于弹出元素后的栈顶元素下标2对应的元素a[2] = 3,不需要再继续弹出, 此时 L[4] = S.top() = 2;然后将元素4对应的下标4存入栈。
此时栈中情况:
(5)至此 算法结束
对应的结果:
a : 5 3 7 4
L : 0 0 2 2
(6)总结
一个元素向左遍历的第一个比它小的数的位置就是将它插入单调栈时栈顶元素的值,若栈为空,则说明不存在这么一个数。然后将此元素的下标存入栈,就能类似迭代般地求解后面的元素
4、模板
stack<int> s;
for(int i = 1; i <= n; ++i)
{
while(s.size() && a[s.top()] >= a[i]) s.pop();
if(s.empty()) l[i] = 0;
else l[i] = s.top();
s.push(i);
}
5、应用
1.给定一组数,针对每个数,寻找它和它左边第一个比它小的数之间有多少个数。
2.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列的长度最大。
3.给定一序列,寻找某一子序列,使得子序列中的最小值乘以子序列所有元素和最大。
例题:http://poj.org/problem?id=3250
大概意思是向右遍历找第一个大于i的数,可以直接套模板了((
#include <iostream>
#include <algorithm>
#include <stack>
#define ll long long
int a[100010]={0};
int r[100010]={0};
using namespace std;
int main()
{
ios::sync_with_stdio(false);
int n,i;
cin>>n;
for(i=1;i<=n;i++) cin>>a[i];
stack <int> sk;
for(i=n;i>=1;i--){
while(sk.size()&&a[sk.top()]<a[i]) sk.pop();
if(sk.empty()) r[i]=n+1;
else r[i]=sk.top();
sk.push(i);
}
ll sum=0;
for(i=1;i<=n;i++){
sum+=r[i]-i-1;
}
cout<<sum<<endl;
return 0;
}