【單調棧優化】POJ-2559——Largest Rectangle in a Histogram

前言

“幾百年前”就學過【單調隊列\單調棧 優化DP】...

“幾百年後”再次學習...畢竟我太弱了,感覺學多少遍都不能完全掌握QAQ

題目

A histogram is a polygon composed of a sequence of rectangles aligned at a common base line. The rectangles have equal widths but may have different heights. For example, the figure on the left shows the histogram that consists of rectangles with the heights 2, 1, 4, 5, 1, 3, 3, measured in units where 1 is the width of the rectangles:


Usually, histograms are used to represent discrete distributions, e.g., the frequencies of characters in texts. Note that the order of the rectangles, i.e., their heights, is important. Calculate the area of the largest rectangle in a histogram that is aligned at the common base line, too. The figure on the right shows the largest aligned rectangle for the depicted histogram.

Input

The input contains several test cases. Each test case describes a histogram and starts with an integer n, denoting the number of rectangles it is composed of. You may assume that 1<=n<=100000. Then follow n integers h1,...,hn, where 0<=hi<=1000000000. These numbers denote the heights of the rectangles of the histogram in left-to-right order. The width of each rectangle is 1. A zero follows the input for the last test case.

Output

For each test case output on a single line the area of the largest rectangle in the specified histogram. Remember that this rectangle must be aligned at the common base line.

Sample Input

7 2 1 4 5 1 3 3
4 1000 1000 1000 1000
0

Sample Output

8
4000

Hint

Huge input, scanf is recommended.

題目大意

有n個矩形,把他們緊挨在一起,求組成的最大的矩形的面積

分析

如果確定了長方形的左端點L和右端點R,那麼最大可能的高度就是min{h[ i ] | L ≤ i < R}。

這樣我們就得到了一個O(n^3)的算法。

如果對計算區間最小值進行一些優化, 那麼可以把複雜度降爲0(n^2)。
枚舉區間,再找區間最小值的算法時間降不下來。

換一個角度考慮問題,我們枚舉每個矩形長條i,求出以h[ i ]爲最小高度的最大面積是多少。

問題的解一定包含在這些答案中。

設面積最大的長方形的左端是L,右端是R,高度是H。如果h[ L-1 ] ≥ H,那麼左端點就可以更新爲L-1,
從而可以得到更大的長方形。

這與假設矛盾,因此h[ L-1 ] <H。

同理可得h[ R ]<H,並且高度H=min{ h[ i ] | L≤i<R}。

因此,我們固定可以給出這樣的H的並進行分析。

此時,L是滿足h[  j -1 ]<h[ i ],的最大的 j ( j ≤ i ),R是滿足h[ j ]<h的最小的 j ( j > i )。

我們把這兩個值分別表示爲L[ i ]和R[ i ],則:
L[ i ]=( j ≤ i 並且h[ j-1 ]<h[ i ]的最大的 j )
R[ i ]=( j > i 並且h[ j ]>h[ i ]的最小的 j )

如果能求出L[ i ]和R[ i ],那麼最大的面積就是max { h[ i ] * ( R[ i ] - L[ i ] ) | 0 ≤ i < n }。


怎麼高效地求出L[ i ]和R[ i ]呢?

我們需要找到每個H[ i ]分別向左右能夠擴展到的連續長度,能夠擴展的條件爲:

區間內的高度值都大於等於H[i]。

先考慮如何計算L[ i ], R[ i ]可以類比解決。
最暴力的方法是從H[ i-1 ]開始倒序枚舉一遍,找出在H[ i-1 ]..H[ 1 ]中第一個小於H[i]的位置。

顯然,隨後在計算L[ i+1 ]時,要做許多重複的比較。
思考後發現,那些H[ j ] >= H[ i ]( 1<=j<=i-1 )的位置在後續計算中是無用的。

因爲H[ i ]比它們小,且更靠右,在後續計算中, H[ i ]必然更容易滿足條件。

這樣, DP轉移的決策就具備單調性。

我們需要維護一種數據結構,在從左到右枚舉到H[ i ]時,能夠快速地把離它最近的左側小於H[ i ]的位置找出來。

這是一個後進先出的特點。於是我們選擇【棧】這種數據結構。

接下來分析這個棧中元素有什麼特別之處:
對於當前的H[ i ],如果棧頂元素大於等於它,不滿足H[ j ] < H[ i ]( 1<=j<=i-1 )的條件,不是L[ i ]的解,並且根據上面的解釋,也不會是L[ i+1 ]..L[ n ]的解,於是彈出棧。
重複上述操作,直到遇到第一個H[ j ] < H[ i ]( 1<=j<=i-1 )的位置,即是所求,即找到L[ i ]所需要的位置

Ps.有可能棧爲空,此時L[ i ]=i

隨後,把H[ i ]壓入棧。
可以看出,整個棧從底到頂的元素保持從小到大的單調順序,因此稱爲【單調棧】。

由於每個元素最多入棧和出棧一次,所以時間複雜度O(n)。下面的代碼使用手寫棧。

注意:

一般手寫單調棧秉持這樣的一個原則:【左閉右開】

即左端點L所指位置爲合法的值,右端點R所指位置爲空,R-1纔是最末端的合法位置

所以上述說明中有關右端點的都是“ < ”或“ i-1 ”

代碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5;
ll h[MAXN+5],st[MAXN+5],L[MAXN+5],R[MAXN+5];
int n;
void Solve()
{
	memset(st,0,sizeof(st));
	//計算l
	int top=0;//棧的大小(棧存下標) 
	for(int i=1;i<=n;i++)
	{
		while(top>0&&h[st[top-1]]>=h[i])
			top--;
		L[i]=top==0?1:(st[top-1]+1);
		st[top++]=i;
	} 
	//計算R
	top=0;
	for(int i=n;i>=1;i--)
	{
		while(top>0&&h[st[top-1]]>=h[i])
			top--;
		R[i]=top==0?n+1:(st[top-1]);
		st[top++]=i;
	} 
	ll res=0;
	for(int i=1;i<=n;i++)
		res=max(res,(ll)h[i]*(R[i]-L[i]));
	printf("%lld\n",res);
}
int main()
{
	while(~scanf("%d",&n))
	{
		if(!n)
			break;
		for(int i=1;i<=n;i++)
			scanf("%lld",&h[i]);
		Solve();
	}
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章