HISTOGRA - Largest Rectangle in a Histogram

本文介绍了三种求解最大子矩阵面积的方法:预处理左右延伸终点、单调栈和笛卡尔树。通过实例讲解了每种方法的实现思路和核心代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:
如图所示,在一条水平线上有n个宽为1的矩形,求包含于这些矩形的最大子矩形面积(图中的阴影部分的面积即所求答案)。

输入格式:

有多组测试数据,每组数据占一行。输入零时读入结束。

每行开头为一个数字n(1<=n<=100000),接下来在同一行给出n个数字h1h2…hn(0<=hi<=1000000000)表示每个矩形的高度。

输出格式:

对于每组数据,输出最大子矩阵面积,一组数据输出一行。

做法一:我们预处理出每一个矩形可以最多向左右延伸的终点

l,r数组分别代表第i个矩形最多可以向左右扩展的矩形位置,也就是找到第一个比它矮的矩形,核心代码就是那个while循环。
预处理出来这两个数组之后我们就可以总结答案。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
long long read(){
	char s;
	long long f=1,x=0;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
int n;
long long l[1000010],r[1000010];
long long h[1000010];
int main(){
	while(1){
		n=read();
		if(n==0)return 0;
		memset(h,0,sizeof(h));
		memset(l,0,sizeof(l));
		memset(r,0,sizeof(r));
		for(int i=1;i<=n;i++){
			h[i]=read();
		}
		for(int i=1;i<=n;i++){
			l[i]=i;
			while(l[i]-1>=0&&h[l[i]-1]>=h[i]){
				l[i]=l[l[i]-1];
			}
		}
		for(int i=n;i>0;i--){
			r[i]=i;
			while(r[i]+1<=n&&h[r[i]+1]>=h[i]){
				r[i]=r[r[i]+1];
			}
		}
		long long ans=0;
		for(int i=1;i<=n;i++){
			ans=max(ans,h[i]*(r[i]-l[i]+1));
		}
		cout<<ans<<endl;
	}
} 

做法二:我们可以用单调栈来更快地找到法一中的左右延伸的终点

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
long long read(){
	char s;
	long long f=1,x=0;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
long long n;
long long ans=0;
long long st[1000010];
long long h[1000010];
long long w[1000010];
int main(){
	while(1){
		n=read();
		if(n==0)return 0;
		memset(h,0,sizeof(h));
		int top=-1;
		ans=0;
		for(int i=1;i<=n;i++){
			h[i]=read();
		}
		h[n+1]=0;
		for(int i=1;i<=n+1;i++){
			if(top==-1||h[st[top]]<=h[i]){
				st[++top]=i;
				w[st[top]]=1;
			}
			else{
				long long wide=0;
				while(top>=0&&h[st[top]]>h[i]){
					wide+=w[st[top]];
					ans=max(ans,wide*h[st[top]]);
					top--;
				}
				st[++top]=i;
				w[st[top]]=wide+1;
			}
		}
		cout<<ans<<endl;
	}
}

做法三:笛卡尔树
1.介绍一下笛卡尔树
笛卡尔树的每一个节点都有两个值,val与key。val是原数组的值,key是下标
笛卡尔树有两个特点:1.任意一颗子树的中序遍历结果表示数组的一段区间
2.笛卡尔树的val值满足大(小)根堆的性质

首先关于建树:
我们用一个stack来记录最右端的一条链中的节点。
这个栈是一个单调栈,我们从左向右遍历每一个节点。
有以下几条规则:1.由于第一个特点(任意一颗子树的中序遍历结果表示数组的一段区间),我们每新加入一个节点它目前只能有左儿子,且只能作为右儿子或者根节点。

具体步骤如下:1.在栈中找到第一个val值小于(这道题维护的是小根堆)的节点,把该节点以下的节点均踢出栈。2.这个新加节点就是它的右儿子,如果有被踢出的节点,则最后一个被踢出的就是新加节点的左二子。由于STL中的栈很难记录被踢出的上一个元素,所以我们用一个数组来维护一个栈。3.将这个新节点入栈

核心代码:

void build(){
	memset(st,0,sizeof(st));
	int top=-1;
	int k;
	for(int i=1;i<=n;i++){
		k=top;
		while(k>=0&&t[st[k]].val>=t[i].val)k--;
		if(k!=-1){//如果还有元素,也就是它有父亲节点
			t[st[k]].rs=i;		
			t[i].fa=st[k];	
		}
		if(k<top){//如果有儿子
			t[i].ls=st[k+1];
			t[st[k+1]].fa=i;
		}
		st[++k]=i;//入栈
		top=k;
	}
	root=st[0];
}

以下为完整代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
long long read(){
	char s;
	long long x=0,f=1;
	s=getchar();
	while(s<'0'||s>'9'){
		if(s=='-')f=-1;
		s=getchar();
	}
	while(s>='0'&&s<='9'){
		x*=10;
		x+=s-'0';
		s=getchar();
	}
	return x*f;
}
int n;
struct tree{
	int key;
	long long val;
	int ls,rs,fa;
	long long size;
}t[500050];
long long st[500050];
int root=0;
void build(){
	memset(st,0,sizeof(st));
	int top=-1;
	int k;
	for(int i=1;i<=n;i++){
		k=top;
		while(k>=0&&t[st[k]].val>=t[i].val)k--;
		if(k!=-1){
			t[st[k]].rs=i;		
			t[i].fa=st[k];	
		}
		if(k<top){
			t[i].ls=st[k+1];
			t[st[k+1]].fa=i;
		}
		st[++k]=i;
		top=k;
	}
	root=st[0];
}
long long ans=-1;
void dfs(int rt){
	if(t[rt].ls!=-1){
		dfs(t[rt].ls);
		t[rt].size+=t[t[rt].ls].size;
	}
	if(t[rt].rs!=-1){
		dfs(t[rt].rs);
		t[rt].size+=t[t[rt].rs].size;
	}
	ans=max(ans,t[rt].size*t[rt].val);
	return;
}
int main(){
	while(1){
		n=read();
		if(n==0)return 0;
		ans=-1;
		for(int i=1;i<=n;i++){
			t[i].val=read();
			t[i].key=i;
			t[i].ls=t[i].rs=-1;
			t[i].size=1;
			t[i].fa=-1;
		} 
		build();
		dfs(root);
		cout<<ans<<endl;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值