题意:
给出1个M*N的矩阵M1,里面的元素只有0或1,找出M1的一个子矩阵M2,M2中的元素只有1,
并且M2的面积是最大的。输出M2的面积。
思路:
先说一种单调栈的做法,
方法是:
1、先将0/1矩阵读入x,对每一个非零元素x[i][j],将其更新为:在本行,它前面的连续的1的个数+1
(+1表示算入自身)比如,若某一行为0 1 1 0 1 1 1,则更新为0 1 2 0 1 2 3
2、对每一个非零元素x[i][j],在第j列向上和向下扫描,直到遇到比自身小的数,若扫描了y行,则得
到一个大小为x[i][j]*(y+1)的全1子矩阵(+1表示算入自身所在行)
比如,若某一列为[0 3 4 3 5 2 1]'(方便起见,这里将列表示成一个列向量),我们处理这一列的
第4个元素,也就是3,它向上可以扫描2个元素,向下可以扫描1个元素,于是得到一个4×3的全1子矩阵。
3、在这些数值中取一个最大的。
思想大概如下图所示(空白处的0没有标出)
对照步骤2中给出的例子,蓝色的箭头表示向上向下扫描,黑色的框表示最终得到的全1子矩阵
这样做为什么是对的?
想一想,对那个最大的全1子矩阵,用这种方法能不能找到它呢?——肯定可以。
一个最大全1子矩阵,肯定是四个边界中的每一个都不能再扩展了,如下图
假设图中全1子矩阵就是最大子矩阵,则左边界左侧那一列肯定有一个或多个0(否则就可以向
左边扩展一列,得到一个更大的全1矩阵)
对其他3个边界有类似的情况。
//复杂度 O(n2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e5+10;
int m,n,a;
int f[555][555];
int l[555],r[555];
int main(){
scanf("%d %d",&m,&n);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a);
if(a)
f[i][j]=f[i][j-1]+1;
else
f[i][j]=0;
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
stack<int>s,st;
l[1]=1,r[m]=m;
for(int j=1;j<=m;j++)
{
if(s.empty())
{
s.push(j);
}
else
{
if(f[s.top()][i]>=f[j][i])
{
while(!s.empty()&&f[s.top()][i]>=f[j][i])
{
s.pop();
}
if(s.empty())
{
l[j]=1;
}
else
{
l[j]=s.top()+1;
}
s.push(j);
}
else
{
l[j]=j;
s.push(j);
}
}
}
for(int j=m;j>=1;j--)
{
if(st.empty())
{
st.push(j);
}
else
{
if(f[st.top()][i]>=f[j][i])
{
while(!st.empty()&&f[st.top()][i]>=f[j][i])
{
st.pop();
}
if(st.empty())
{
r[j]=m;
}
else
{
r[j]=st.top()-1;
}
st.push(j);
}
else
{
r[j]=j;
st.push(j);
}
}
}
for(int j=1;j<=m;j++)
{
ans=max(ans,(r[j]-l[j]+1)*f[j][i]);
}
}
printf("%d\n",ans);
return 0;
}
再说一种我自己的做法,可以预处理出每一列的前缀和,这样我们就可以枚举两行i,j.将i到j的
每一列和和压缩成一个数b【k】,从而变成一个一维数组,然后比较b【k】中1的个数是否等于高,
从而判断是否全是1,并记录最大的矩阵.
//复杂度On3
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e5+10;
int n,m,a;
int sum[555][555],b[555];
int main(){
scanf("%d %d",&m,&n);
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&a);
sum[i][j]=sum[i-1][j]+a;
}
}
int ans=0;
for(int i=1;i<=m;i++)
{
for(int j=i;j<=m;j++)
{
for(int k=1;k<=n;k++)
b[k]=sum[j][k]-sum[i-1][k];
int cnt=0;
for(int k=1;k<=n;k++)
{
if(b[k]==(j-i+1))
cnt++;
else
{
ans=max(ans,cnt*(j-i+1));
cnt=0;
}
}
ans=max(ans,cnt*(j-i+1));//这里记得需要最后算一次,因为上面的循环最后一列不一定不满足,哪样就少算了一个
}
}
printf("%d\n",ans);
return 0;
}