[poj2559]Largest Rectangle in a Histogram(多种方法详细介绍)

Largest Rectangle in a Histogram
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 33533 Accepted: 10940
Description

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.
Source

Ulm Local 2003

题意:

给定n个柱子(1<=n<=100000),用来表示柱状图中各个柱子的高度(0<=hi<=1000000000). 每个柱子彼此相邻,且宽度为 1 .
求在该柱状图中,能够勾勒出来的矩形的最大面积。

题目在此

思路分析:

n已经到10^5了,时间复杂度只能考虑O(nlgn)和O(n). 不过因为这道题比较经典,可以不用管时间复杂度,尽可能多的尝试各种方法。
首先,矩形最大面积必然是连续的x,面积公式为S = (xt-x0)*miny. miny是[x0,xt]里的最小值。这时候可以很自然而然的想到枚举[1,n]中的所有区间:[1,1], [1,2], [1,2,3], [1,2,3,…,n] [2,3], [2,3,…n] …
于是有了方法一:dfs
用dfs生成子集的方式枚举区间,计算出miny。时间复杂度O(n^2).
下面上dfs代码。

#include <iostream>
#define MAX 100010
using namespace std;

int Y[MAX];

int s, n, tmp, Max, min_y;

void dfs(int cur)	//枚举x的子集 
{
	if(cur >= n)
		return;
	min_y = min(min_y,Y[cur]);
	tmp = (cur-s+1)*min_y;	//记录当前x子集的面积
	Max = max(tmp,Max);
	cout<<"区间:"<<s<<","<<cur<<" "<<tmp<<endl; 
	dfs(cur+1);
}

int main()
{
	cin>>n;
	for(int i = 0;i < n;i++) cin>>Y[i];
	for(int i = 0;i < n;i++)
	{
		s = i;
		min_y = INT_MAX;
		dfs(i);
	}
	cout<<Max<<endl;
	return 0;
} 

接下来,对O(nlgn)和O(n)的时间复杂度分析,lgn是一种分治算法和树的复杂度,看看能不能从分治和树里面找到什么规律。
首先看看分治,假设k为切点,我们将[1,n]区间分成[1,k-1],[k+1,n]分析一下。这时候最大矩形面积可能完全在左半区间,右半区间,也可能在两个区间的中间。左半区间和右半区间容易操作,这个直接分治就行。那么怎么处理中间这种情况呢?
我们可以把切点定在高度最小的柱子编号上,也就是当x=k时y最小。这样来看涉及到中间这部分的最大子面积就是(n-1)*h[k]了。因此只需要对这三种情况取最大值就是答案。
知道了分治的实现算法,先别急着写。我们找y的最小值所在位置是通过遍历区间。如果该矩形为升序,那就意味着时间复杂度依然是O(n^2). 还是会被无情的TLE.
这时候我们就应该想一下如何对求y最小值进行时间上的优化。其实这个问题可以转化为求区间最小值下标。这种区间问题总会想到线段树吧(前提是得知道线段树是什么)。这里就不再对线段树进行介绍了。下面直接上因为自己菜对线段树debug许久才能AC的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAX 100010
#define MAX_NUM 1000000001 
#define ll long long 
using namespace std;

ll h[MAX];	//记录高度 
ll Min[MAX<<2], pos[MAX<<2];	//Min记录节点最小值,pos记录位置 
ll record[MAX<<2]; 	//用作查询时向上更新位置的工具,目的是不破坏pos所管区间的最小值位置 
ll n;

inline void PushUp(ll rt)	//更新节点 
{
	if(Min[rt<<1] <= Min[rt<<1|1]) pos[rt] = pos[rt<<1];	//更新节点最小值位置 
	else pos[rt] = pos[rt<<1|1];
	Min[rt] = min(Min[rt<<1], Min[rt<<1|1]);	//更新节点最小值 
//	cout<<pos[rt]<<" "<<Min[rt]<<endl;
}


void build(ll rt, ll l, ll r)
{
	if(l == r)
	{
//		cout<<l<<"*"<<h[l]<<endl;
		pos[rt] = l;	 
		record[rt] = l;
		Min[rt] = h[l];
		return;
	}
	ll mid = (l+r)>>1;
	build(rt<<1, l, mid);
	build(rt<<1|1, mid+1, r);
	PushUp(rt);
}

ll Query(ll rt, ll l, ll r, ll L, ll R, ll &P)	//表示[L,R]区间最小值的位置为P 
{
	if(L <= l && R >= r)
	{
		P = pos[rt];
		record[rt] = pos[rt];
		return Min[rt];
	}
	ll mid = (l+r)>>1;
	ll x1 = MAX_NUM, x2 = MAX_NUM;
	if(L <= mid) x1 = Query(rt<<1, l, mid, L, R, P);
	if(R > mid) x2 = Query(rt<<1|1, mid+1, r, L, R, P);
//	cout<<x1<<" "<<x2<<endl;
//	cout<<P<<endl;
/*--	
record[rt]=P,查询的时候需要向上更新位置,因为原来的pos[rt]是指的rt节点自己的区间最小值位置
比如说查询区间[3,4],会递归到[4,4],然后回溯到[4,5],[4,6],[1,6],如果不更新位置 [4,4]的pos会被pos[4,6],
pos[1,6]代替,pos[4,4]的值就丢失了。因此需要让record[rt]=P,用record代替pos向上更新pos[4,6],pos[1,6]的值
不能直接用pos向上更新,因为更新完之后pos[rt]的值不再是rt节点区间的最小值位置了。再次查询数据就有可能不对。 
--*/
	if(x1 <= x2) P = record[rt<<1], record[rt] = P;	 
	else P = record[rt<<1|1], record[rt] = P; 
//	cout<<"位置:"<<P<<endl; 
	return min(x1,x2); 
}


ll max(ll a, ll b, ll c)
{
	if(a < b) a = b;
	if(a < c) a = c;
	return a;
}

ll maxArea(int l, int r)
{
	if(l >= r) return h[l];
	ll sign = l;
	ll tmp = Query(1,1,n,l,r,sign);
//	cout<<"区间:"<<l<<","<<r<<endl;
//	cout<<sign<<" "<<tmp<<endl;
//	cout<<sign<<l<<r<<endl;
	return max(h[sign]*(r-l+1), maxArea(l,sign-1), maxArea(sign+1,r));
}

int main()
{
	while(cin>>n && n)
	{
		for(int i = 1;i <= n;i++) scanf("%lld",&h[i]), Min[i] = MAX_NUM;
		build(1,1,n);
		ll tmp, sign;
//		tmp = Query(1,1,n,3,4,sign);
//		cout<<tmp<< " "<< sign<<endl;
		ll ans = maxArea(1,n);
		cout<<ans<<endl;
	}
	return 0;
}

接下来,我们看看O(n)的时间复杂度能不能做。如果在遍历的时候能把当前的最大子面积给求出来,那就能做!然后就开始找一下规律。
emm…反正是用到单调栈了。维护单调递增栈。因为当矩形高度呈递增状态的时候我们可以通过下标算出当前栈里每一个矩形能够达到的最大子面积,在这些矩形的最大子面积里取最大值就行。比如说矩形高度为[1,4,7],编号为1,2,3. 那么编号3矩形的最大子面积为71. 编号2矩形的最大子面积为42. 编号1矩形的最大子面积为1*3. 然后就是分析情况,找出公式规律了。下面上leetcode的官方题解,很详细。
leetcode官方题解
我的代码:

#include <iostream>
#include <cstdio> 
#include <stack>
#define ll long long 
#define MAX 100010
using namespace std;

int main()
{
	ll h[MAX], n;
	while(cin>>n && n)
	{
		ll maxArea = 0;	
		stack<int> st;
		st.push(-1);
		for(int i = 0;i < n;i++)
		{
			scanf("%lld",&h[i]);
			if(st.top() == -1 || h[i]>=h[st.top()])	//栈底为-1或者高度为递增状态,进栈 
				st.push(i);
			else
			{
				while(h[i] < h[st.top()] && st.top() != -1)	//高度比栈顶小,一直出栈直到比栈顶大,维护递增栈 
				{
					ll t = st.top();
					st.pop();
					maxArea = max(maxArea, (i-st.top()-1)*h[t]);	//出栈的时候计算该矩形的最大子面积 
//					cout<<"出"<<t<<" "<<maxArea<<endl;
				}
				st.push(i);	//出栈完后,进栈 
//				cout<<i<<endl; 
			}
		}
		ll tt = st.top();
		while(st.top() != -1)	//最后,将栈里所有元素出栈,计算最大子面积 
		{
			ll t = st.top();
			st.pop();
			maxArea = max(maxArea, (tt-st.top())*h[t]);
//			cout<<"出"<<t<<" "<<maxArea<<endl;
		}
		cout<<maxArea<<endl;
		return 0;
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值