悬线法 求解满足条件的最大子矩阵。
悬线:由一个点向下扩展所得到的合法线段。
L(i,j):从(i,j)向左扩展,最左端的合法位置。
R(i,j):从(i,j)向右扩展,最右端的合法位置。
h(i,j):从(i,j)(i,j)向上扩展,最上端的合法位置,即以(i,j)为下端点的悬线上端点。
状态转移 对于每个点(i,j)
若可以向上扩展,则
h(i,j)=h(i−1,j)+1
L(i,j)=max(L(i,j),L(i−1,j)) 该点最左边第一个合法的位置(即无障碍点)
R(i,j)=min(R(i,j),R(i−1,j))同理
-------------------------------------------------------------------------------------------
该题即是求全1次大的子矩阵 详解见代码
需注意的就是有一个需要标记的地方,因为可能有多个子矩阵面积大小一样但位置不同
#include<cstdio>
#include<cstring>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<stdlib.h>
#include<queue>
using namespace std;
const int inf=0x3f3f3f3f;
const int maxn=1000000;
const int N=1005;
int n,m;
int a[N][N];//障碍点为1 不是为0
int L[N][N],R[N][N],l[N][N],r[N][N],h[N][N];
int ans=0;
priority_queue<int> q;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
for (int j=1;j<=m;++j){
char ch;
cin>>ch;
if (ch=='0') a[i][j]=1;//障碍点
else a[i][j]=0;
}
for(int i=1;i<=n;++i){//预处理障碍点
int Max=0,Min=m+1;
for(int j=1;j<=m;++j){
if (a[i][j]) Max=max(Max,j);
L[i][j]=Max;//左边第一个障碍点的位置
}
for(int j=m;j>=1;--j){
if (a[i][j]) Min=min(Min,j);
R[i][j]=Min;//右边第一个障碍点的位置
}
}
for(int j=1;j<=m;++j)
r[0][j]=m+1;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
if(a[i][j]){
h[i][j]=0;
l[i][j]=0,r[i][j]=m+1;
}
else{
h[i][j]=h[i-1][j]+1;
l[i][j]=max(l[i-1][j],L[i][j]);
r[i][j]=min(r[i-1][j],R[i][j]);
}
}
int tmp1=0;
int ll=0;
int rr=0;
int hh=0;
int bb=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
int area=((r[i][j]-l[i][j]-1)*h[i][j]);
if(tmp1<area){//找第一个最大的 标记长 宽 高
ll=l[i][j];
rr=r[i][j];
hh=h[i][j];
bb=i;
tmp1=area;
}
}
// cout<<tmp1<<endl;
int tmp2=0;
tmp2=max(tmp2,(rr-ll-2)*hh);//特判最大的矩形下少一行或一列的面积
tmp2=max(tmp2,(rr-ll-1)*(hh-1));
for(int i=1;i<=n;i++){//找次大
for(int j=1;j<=m;j++){
if(hh==h[i][j]&&ll==l[i][j]&&rr==r[i][j]&&bb==i) continue;
int area=((r[i][j]-l[i][j]-1)*h[i][j]);
if(tmp2<area){
tmp2=area;
}
}
}
cout<<tmp2<<endl;
return 0;
}