单调栈:
单调栈解决的是:以某个值为最小(最大)值的最大区间。
实现方法是:求最小值(最大值)的最大区间,维护一个递增(递减)的栈,当遇到一个比栈顶小的值的时候开始弹栈,弹栈停止的位置到这个值的区间即为此值左边的最大区间;同时,当一个值被弹掉的时候也就意味着比它更小(更大)的值来了,也可以计算被弹掉的值的右边的最大区间。
单调递增:数据出栈的序列为单调递增序列(即从栈顶到栈底的元素是单调递增的)
单调递减:数据出栈的序列为单调递减序列(与上面相反)
模板如下:
1 stack<int> sta; 2 for (遍历这个数组) 3 { 4 while(栈不为空 && 栈顶元素小于当前元素){ 5 更新结果; 6 栈顶元素出栈; 7 } 8 if(栈空 || 栈顶元素大于等于当前比较元素){ 9 当前数据入栈; 10 } 11 }
或
1 stack<int> sta; 2 for (遍历这个数组) 3 { 4 if(栈空 || 栈顶元素大于等于当前比较元素){ 5 入栈; 6 } 7 else{ 8 while(栈不为空 && 栈顶元素小于当前元素){ 9 更新结果; 10 栈顶元素出栈; 11 } 12 当前数据入栈; 13 } 14 } 15
/*如果要使最后单调栈内的元素能全部弹出,一般要在数组最后加一个符合条件的值,使栈内元素能全部弹出。*/
例题:Largest Rectangle in a Histogram
题意
求所给出的条形图可形成的最大矩形面积。
题解
以每一个矩形的高度作为最终大矩形的高度,看最宽能是多少,然后统计最优解。(暴力法O(n^2)超时)
维护单调栈(单调递减),当前高度数值大于栈顶元素大小则入栈,小于则出栈。在维护单调性的弹出操作时统计宽度,不断更新答案即可得到最优解。
Code
STL的stack:
1 #include<cstdio> 2 #include<stack> 3 using namespace std; 4 typedef long long ll; 5 const int maxn=1e5+5; 6 ll h[maxn]; 7 int main() 8 { 9 ll n; 10 while(~scanf("%lld",&n)&&n){ 11 for(int i=1;i<=n;i++){ 12 scanf("%lld",&h[i]); 13 } 14 h[n+1]=0;//为了让栈内的元素最后能全部弹出,令h[n+1]=0。因为是多测试用例,所以这里必须重新初始化,否则可能会受前一次测试用例影响 15 ll area,Max=-1; 16 stack<pair<ll,ll> >S;//第一个存高度,第二个存当前高度可达到的左端 17 for(int i=1;i<=n+1;i++){ 18 ll L=i; 19 while(!S.empty()&&h[i]<=S.top().first){ //出栈 20 L=S.top().second; 21 area=(i-L)*S.top().first;//L为矩形左边界,当前i为右边界,i-L为矩形的宽,S.top().first为矩形的高 22 Max=max(area,Max); 23 S.pop(); 24 } 25 if(S.empty()||h[i]>S.top().first)//入栈 26 S.push({h[i],L}); 27 } 28 printf("%lld\n",Max); 29 } 30 return 0; 31 }
手写栈:
1 /*AC 109ms*/ 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 typedef long long ll; 6 const int MAX=1e5+5; 7 using namespace std; 8 ll h[MAX],w[MAX],stack[MAX]; 9 int main() 10 { 11 int n; 12 while(scanf("%d",&n)&&n) 13 { 14 memset(h,0,sizeof(h));//多测试用例,必须要重新初始化 15 ll ans=0,k;///k为右宽 16 for(int i=1;i<=n;i++){ 17 scanf("%lld",&h[i]); 18 } 19 20 h[n+1]=0;//为了方便把最后剩下的单调递增的矩形也统计进去,我们假设h[n+1]的位置有一个高度为0的矩形,最后将它加入单调栈时他会将所有矩形都弹出,那么答案也就完成最后的更新了。 21 int top=0; 22 stack[0]=-1;///注意这里要将单调栈的0下标位置初始化为-1,否则有可能会在下面while处陷入死循环导致超时 23 24 for(int i=1;i<=n+1;i++){ 25 if(h[i]>stack[top]){///严格单调递减 26 stack[++top]=h[i];///入栈 27 w[top]=1;//左宽为1,即该入栈元素本身 28 } 29 else{ 30 k=0;///第一个出栈元素右宽为0 31 while(h[i]<=stack[top]){ 32 w[top]+=k;///算出该出栈元素的总宽(左宽+右宽) 33 ans=max(stack[top]*w[top],ans);///计算以该出栈元素为高的矩形面积,更新最优解 34 k=w[top];///下一个出栈元素的右宽为上一个出栈元素的总宽 35 top--; 36 } 37 stack[++top]=h[i];///入栈 38 w[top]=1+k;//该入栈元素左宽为最后一个出栈元素的总宽+1 39 } 40 } 41 printf("%lld\n",ans); 42 } 43 return 0; 44 }