单调栈

从某一个元素开始,以该元素为最值,一直向前延伸找最优值(若需要两个方向的解,则再从右往左扫描一遍即可).每个元素最多进出栈一次,复杂度O(n)

应用:

①栈维护上升序列求解不小于i的最大区间: 

题目链接: hdu1506 Largest Rectangle in a Histogram 

求直方图能覆盖的最大矩阵的面积,则矩阵的高度为其中直方图中的最小高度。对某个直方图h[i],向两边搜寻不小于该高度的区间。

以左边界为例,使用单调栈维护一个递增序列的下标,每次取栈顶j进行比较。若h[j] >= h[i],则说明i可以延伸至j的左边界,则更新i的左边界。由于高度小于h[i]的直方图可以覆盖i的区间,大于h[i]的直方图直接被i中断,所以j对后续节点并无作用,将j弹出栈。

直到h[j] < h[i],说明i不能再向更远处延伸,将i进栈。

代码如下:

#include <cstdio>
#include <stack>
using namespace std;
#define N 100005
int a[N], l[N], r[N];
stack<int> h;

int main(){
    int n;
     while(scanf("%d", &n) && n){
        h.push(0);
        for(int i = 0; i < n; i ++){
             scanf("%d", &a[i]);
            l[i] = 1;
            r[i] = 1;
        }
        for(int i = 1; i < n; i ++){
            while(!h.empty() && a[i] <= a[h.top()]){
                l[i] += l[h.top()];
                h.pop();
            }
            h.push(i);
        }
        while(!h.empty())
            h.pop();
        h.push(n - 1);
        for(int i = n - 2; i >= 0; i --){
            while(!h.empty() && a[i] <= a[h.top()]){
                r[i] += r[h.top()];
                h.pop();
            }
            h.push(i);
        }
        while(!h.empty())
                h.pop();
        long long max_val = 0;
        for(int i = 0; i < n; i ++){
            long long temp = (long long)a[i] * (l[i] + r[i] - 1);
            if(temp > max_val)
                max_val = temp;
        }
        printf("%I64d\n", max_val);
    }
}

②栈维护下降序列求解不大于i的最大值

题目链接:hdu3410 Passing the Message 

left messenger为左边不超过h[i]的最高者j.因为若h[j] > h[i],则j一定先从别处得到message。维护一个下降序列的下标,保存当前的最高值、次高值……

每次将h[i]与栈顶比较,若大于栈顶,说明left messenger的位置不晚于这个位置,而且栈顶元素会被i挡住,所以对于i以后的点,栈顶元素也没有意义了,应该出栈。直到小于栈顶的高度,说明i的视线会被栈顶的元素挡住,则上一次的栈顶元素为left messenger。同理,求解right从右往左扫描即可。

代码如下:

#include <cstdio>
using namespace std;
#define N 50005
int h[N], stack[N], L[N], R[N];

int main(){
	int n, tc, ca = 0;
	scanf("%d", &tc);
	while(tc --){
		scanf("%d", &n);
		for(int i = 1; i <= n; ++i)
			scanf("%d", &h[i]);
		stack[0] = 1;   //维护一个下降序列,栈中存放下标
		int top = 0;
		for(int i = 2; i <= n; ++i){
			int cur = 0;
			while(top >= 0 && h[stack[top]] < h[i])
				cur = stack[top], --top;
			L[i] = cur;   //最后一个比h[i]小的位置
			stack[++top] = i;
		}
		stack[0] = n, top = 0;
		for(int i = n - 1; i > 0; --i){
			int cur = 0;
			while(top >= 0 && h[stack[top]] < h[i])
				cur = stack[top], --top;
			R[i] = cur;
			stack[++top] = i;
		}
		printf("Case %d:\n", ++ca);
		for(int i = 1; i <= n; ++i){
			printf("%d %d\n", L[i], R[i]);
		}
	}
	return 0;
}

单调栈不涉及区间长度,可以一直往一个方向延伸,所以只在栈顶进出栈。

当需要在一定范围内搜索时,要用单调队列,队头为该范围内的最优值,当下标不在有效范围时,队头要出队。由于篇幅有限,单调队列令开一篇记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值