AcWing每日一题 3516.最大面积(单调栈,递推)

最大面积

原题链接

给定一个 N×M 的 01 矩阵,矩阵下标从 0 开始。

有 Q 个询问,第 i 个询问为:将矩阵中 (xi,yi) 的元素改成 0 之后,只包含 1 的子矩阵的最大面积是多少。

注意:

每次询问均是独立的。 询问方格内元素可能本来就是 0。 子矩阵的面积是指矩阵的大小。

数据范围
1≤N,M≤2000,1≤Q≤105, 0≤xi<n,0≤yi<m

输入格式
第一行包含两个整数 N,M。
接下来 N 行,每行包含 M 个 01 字符。
再一行包含整数 Q。
接下来 Q 行,每行包含 2 个整数 (xi,yi)。

输出格式
每个询问输出一行一个结果,表示最大面积。

输入样例:
4 2
10
11
11
11
3
0 0
2 0
3 1
输出样例:
6
3
4


这题难度较大,主要是难在思维上,如果懂了的话代码还是比较好写的

首先我们可以把题目中的01矩阵看成一个个矩形,那么题目就变成了求最大矩形面积(先不考虑把某个点置0),
那么最大矩形面积怎么求呢?
我们会发现对于每个小矩形的高度h,所能找到的最大面积矩形
//要使和他相同高度的或者比他高度大的宽尽可能的大,并且要相邻

也就是说
宽是他左边第一个比他(i)小的高度的位置+1(记为l[i])到右边第一个比他小的高度的位置-1(记为r[i])

那么宽度就是(r[i]-l[i]+1)
面积就是(r[i]-l[i]+1)*h
在这里插入图片描述
但是这个不一定是所有中的最大面积矩形,所以我们需要找出对于所有位置的高度的最大面积矩形,再取最大值
暴力找要n^2,但是单调栈的话只需要O(n)即可(每个元素进出栈各一次)

那么什么是单调栈呢?
简单来说,就是单调递增或递减的栈

在找左边时
如果当前高度比栈顶元素位置的高度要大,则直接入栈
如果比栈顶位置的高度小或者相等,则出栈到当前高度比栈顶元素位置高度大或者空栈

那么左边第一个比h[i]小的位置就是栈顶元素了
l[i]=栈顶元素-1//注意:栈里存的是i并不是高度
始终保持栈内元素单调递增

代码如下:

//手机双击代码可以全屏看代码哦
int calc(int h[],int n){
	int tot;
	int l[maxn],r[maxn];
	int stack[maxn];
	for(int i=0;i<=n;++i)l[i]=r[i]=stack[i]=0;
	tot=0;
	for(int i=1;i<=n;++i){
		while(tot>0&&h[i]<=h[stack[tot]])tot--;
		l[i]=stack[tot]+1;
		if(!tot)l[i]=1;
		stack[++tot]=i;
	}
	tot=0;
	for(int i=n;i>=1;--i){
		while(tot>0&&h[i]<=h[stack[tot]])tot--;
		r[i]=stack[tot]-1;
		if(!tot)r[i]=n;
		stack[++tot]=i;
	}
	int ans=0;
	for(int i=1;i<=n;++i)
		ans=max(ans,(r[i]-l[i]+1)*h[i]);
	return ans;
}

那么现在我们就能够计算出一个矩阵中的最大1矩阵面积了

但是这还不够
因为随着把某个数置0,会使矩阵的高度发生变化

我们继续往后想,如果吧(i,j)置0,最大面积矩阵一定会出现在他的左侧,右侧,上侧,或者下侧(就是不可能包含这个点)
在这里插入图片描述
那么我们还要处理出四个数组,U[n],D[n],L[m],R[m]
分别表示
上n行出现的最大矩阵面积
下n行出现的最大矩阵面积
左m列出现的最大矩阵面积
右m列出现的最大矩阵面积

处理起来也算简单。
上下枚举行
左右枚举列
递推出高度再计算出最大矩阵面积
再和前面枚举算出的答案取最大值即可

这里有两个计算的小技巧
1.在处理L,R的时候可以将矩阵转置,就可以像U,D一样处理了
2.坐标从1开始,递推时不需考虑越界的情况

代码如下:

//手机双击代码可以全屏看代码哦
//U
	memset(h,0,sizeof(h));
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(mp[i][j])h[i][j]=h[i-1][j]+1; 
		}
		U[i]=calc(h[i],m);
		U[i]=max(U[i-1],U[i]);
	}
	//
	//D
	memset(h,0,sizeof(h));
	for(int i=n;i>=1;--i){
		for(int j=1;j<=m;++j){
			if(mp[i][j])h[i][j]=h[i+1][j]+1;
		}
		D[i]=calc(h[i],m);
		D[i]=max(D[i+1],D[i]);
	}
	//
	//L
	memset(h,0,sizeof(h));
	for(int i=1;i<=m;++i){
		for(int j=1;j<=n;++j){
			if(mp[j][i])h[i][j]=h[i-1][j]+1;
		}
		L[i]=calc(h[i],n);
		L[i]=max(L[i-1],L[i]);
	}
	//
	//R
	memset(h,0,sizeof(h));
	for(int i=m;i>=1;--i){
		for(int j=1;j<=n;++j){
			if(mp[j][i])h[i][j]=h[i+1][j]+1;
		}
		R[i]=calc(h[i],n);
		R[i]=max(R[i+1],R[i]);
	}
	//

最终答案就是,max{ L[y-1], R[y+1], U[x-1],D[x+1]}

AC代码如下:

//手机双击代码可以全屏看代码哦
#include<bits/stdc++.h>
using namespace std;
const int maxn=2003;
int mp[maxn][maxn];
int L[maxn],R[maxn],U[maxn],D[maxn];
int h[maxn][maxn];
int max(int a,int b){
	return a<b?b:a;
}
int max(int a,int b,int c,int d){
	return max(max(a,b),max(c,d));
}
int calc(int h[],int n){
	int tot;
	int l[maxn],r[maxn];
	int stack[maxn];
	for(int i=0;i<=n;++i)l[i]=r[i]=stack[i]=0;
	tot=0;
	for(int i=1;i<=n;++i){
		while(tot>0&&h[i]<=h[stack[tot]])tot--;
		l[i]=stack[tot]+1;
		if(!tot)l[i]=1;
		stack[++tot]=i;
	}
	tot=0;
	for(int i=n;i>=1;--i){
		while(tot>0&&h[i]<=h[stack[tot]])tot--;
		r[i]=stack[tot]-1;
		if(!tot)r[i]=n;
		stack[++tot]=i;
	}
	int ans=0;
	for(int i=1;i<=n;++i)
		ans=max(ans,(r[i]-l[i]+1)*h[i]);
	return ans;
}
int main(){
	int n,m;
	scanf("%d %d\n",&n,&m);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			char inp;
			scanf("%c",&inp);
			mp[i][j]=inp-'0';
		}
		getchar();
	}
		
			
	//U
	memset(h,0,sizeof(h));
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(mp[i][j])h[i][j]=h[i-1][j]+1; 
		}
		U[i]=calc(h[i],m);
		U[i]=max(U[i-1],U[i]);
	}
	//
	//D
	memset(h,0,sizeof(h));
	for(int i=n;i>=1;--i){
		for(int j=1;j<=m;++j){
			if(mp[i][j])h[i][j]=h[i+1][j]+1;
		}
		D[i]=calc(h[i],m);
		D[i]=max(D[i+1],D[i]);
	}
	//
	//L
	memset(h,0,sizeof(h));
	for(int i=1;i<=m;++i){
		for(int j=1;j<=n;++j){
			if(mp[j][i])h[i][j]=h[i-1][j]+1;
		}
		L[i]=calc(h[i],n);
		L[i]=max(L[i-1],L[i]);
	}
	//
	//R
	memset(h,0,sizeof(h));
	for(int i=m;i>=1;--i){
		for(int j=1;j<=n;++j){
			if(mp[j][i])h[i][j]=h[i+1][j]+1;
		}
		R[i]=calc(h[i],n);
		R[i]=max(R[i+1],R[i]);
	}
	//
	int T;
	scanf("%d",&T);
	while(T--){
		int x,y;
		scanf("%d %d",&x,&y);
		x++;y++;
		printf("%d\n",max(L[y-1],R[y+1],U[x-1],D[x+1]));
	}
	return 0;//保持好习惯,就和点赞一样
} 

看完点个赞吧,码字不容易呀

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值