【单调栈模板详解】POJ-2559 Largest Rectangle in a Histogram

【单调栈模板详解】POJ-2559 Largest Rectangle in a Histogram

题意:给定一系列宽度为1高度不定的长条,在(O(n))时间内计算出面积最大的矩形。

首先我们看样例的这幅图,红色指针指向的这一位向右拓展了1位,使得该绿色的矩形成为了最优解。

由此启发,我们发现,如果一个矩形右边的矩形比他高,那该矩形便可以向右扩展。

如图所示,只要右边比当前矩形高,就可以一直向右扩展,因此如果一直保持递增,那前面的便可以一直拓展。

但此时如果来了一个比当前的矮的咋办呢?

如图,我们一直向前找,直到找到一个比新来的还要矮的,那这个比新来的还要矮的之前的所有都可以继续拓展,但是新来的和我们找到的这个矩形中间这段区间内的矩形却不能继续向右拓展了,那么这段区间的矩形的使命就光荣结束了,换句话说,他们对答案的贡献就到此为止了,因为往后继续添加矩形这段区间内的矩形由于被这个新来的卡住了,不管再新来多少个都不能向右拓展了。某种神奇的数据结构便能利用这种性质计算出这段区间对答案的贡献。

主角:单调栈

单调栈首先就是一个栈,只不过我们需要维护这个栈,使栈内的元素具有一定的单调性。注意,我们可以在栈里存一个数组的下标,也就是说栈内的元素是下标,但是我们不要下标具有单调性,我们实际维护的是数组的值,这样也是一个单调栈,就如本题,维护下标更加方便。

根据前面的分析,本题我们需要维护一个单调递增的栈,下面来讲解一下维护的过程。

我们来看之前的那个图,由于新来的打破了栈的单调递增性,我们需要把栈顶的元素依次弹出,直到栈能够符合单调递增性。我们假设新来的这一位下标为x,那么只要栈顶元素比新来的大,我们便弹出栈顶元素,同时我们把栈顶元素的下标到新来的下标之间的距离作为宽度,把栈顶元素的值作为高度,更新答案,这样可以保证我们每次弹出的元素都最多向右拓展到了新来的这个元素为止。

一顿操作之后,剩下的就只剩这二位了。那么此时如果又来了一位比下标x-3还矮的,那我们继续重复上面那个过程,如果来了一位比下标x-3高的,那正和我们的心意,我们啥都不用干,因为他的加入仍然满足单调递增性,直接把他放入栈中,我们只在新来的那位比前面的矮的时候统计贡献。你可能会有疑惑,如果来的小条从头一直递增到结尾呢,那我们不是一次贡献都没有统计过嘛?没错,所以我们需要在所有小条都遍历之后判断一下这个栈是否为空,如果为空,那非常好,说明我们之前的贡献都统计完了。那如果不为空呢?如果不为空,那么不就说明了栈中的元素都能一直拓展到末尾而不被弹出嘛,那么我们只要把栈里的元素一直弹出,弹出的时候用该元素的下标到末尾距离作为宽度,该元素的高作为高度,宽度乘高度作为贡献更新答案即可。

至此,本题的单调栈做法就完美结束了。。

吗?

NONONO!!!

我们来看一看这个:

我们看一下这个红色所指的小条,根据之前的模拟过程,我们会把红色的矩形统计进入答案,但是实际上这个绿色的矩形显然更优啊!我们光顾着向右扩展了,却忽略了一个小条可以同时向右向左拓展。

莫慌莫慌,只需要在单调栈维护的过程中加一句话就能解决这个问题。

当这个红色所指的小条插入时,该小条之前所有比他高的都会被弹出,弹出完毕后我们应当把该小条放入栈里,然而我们不要放他自己的下标,我们要放它往左能够拓展到的最左的那个下标,因为它往左拓展的小条都是比它高的,当它右边来了个比他矮的时候,它向右拓展到比它矮的过程中遇到的也都是比它高的,这样就能保证了它往左往右都得到了最大的拓展!如图所示。

下面是相关代码和注释

#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ll;
const int maxn = 1e6+5;
ll arr[maxn],st[maxn];//st表示模拟栈的数组
ll ans,tail;//tail表示栈尾,最底下设为1
int main(){
	int n;
	while (scanf("%d",&n),n){
		ans = 0;
		tail = 1;
		for (int i = 1; i <= n;i++){
			scanf("%lld",&arr[i]);
			bool flag = false;//标记新来的元素是否破坏单调性
			ll temp;
			while (tail > 1 && arr[i] < arr[st[tail-1]]){
                //维护单调栈并计算贡献
				flag = true;
				temp = st[tail - 1];
				ans = max(ans,(i-st[tail - 1]) * arr[st[tail - 1]]);
				tail--;
			}
			if (flag) {
                //把往左拓展到的最左的下标放入栈里
				st[tail++] = temp;
				arr[temp] = arr[i];
			}
			else 
			st[tail++] = i;//如果放入后还满足单调性就直接放入栈中
		}
		while (tail > 1){
            //如果结束之后栈还右元素就计算他们拓展到末尾的贡献
			ans = max(ans,(n-st[tail-1]+1) * arr[st[tail-1]]);
			tail--;
		}
		printf("%lld
",ans);	
	}
	return 0;
}
原文地址:https://www.cnblogs.com/hznudreamer/p/13762643.html