原题链接
给定一个 N×M 的 01 矩阵,矩阵下标从 0 开始。
有 Q 个询问,第 i 个询问为:将矩阵中 (xi,yi) 的元素改成 0 之后,只包含 1 的子矩阵的最大面积是多少。
注意:
每次询问均是独立的。 询问方格内元素可能本来就是 0。 子矩阵的面积是指矩阵的大小。
数据范围
1≤N,M≤2000,1≤Q≤105, 0≤xi
输入格式
第一行包含两个整数 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
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;//保持好习惯,就和点赞一样
}
看完点个赞吧,码字不容易呀