大概一个月以前写的这道题现在才想起来写博客0.0
因为这道题能用两种方法做
solution:
1、单调栈
棋盘上求极大子矩阵的问题,可以先将题目中的条件变形转化成简单的问题,这道题可以对于为0而且横纵坐标奇偶性不同的标为1,为1而且横纵坐标奇偶性相同的标为1;对于为1而且横纵坐标不同的标为0,对于为0而且横纵坐标相同的标为0,转化成最大全0子矩阵问题,再用单调栈首先处理处每一列或者每一行的,然后将矩阵压缩成行,用单调栈就可以实现
代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define maxn 2005
using namespace std;
int n,m,a[maxn][maxn],row[maxn][maxn],line[maxn][maxn],stk[maxn],top,to[maxn];
int ans1,ans2;
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
void pre(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(((i&1)==(j&1) && a[i][j])||((i&1)!=(j&1) && !a[i][j]))
a[i][j]=1;
else a[i][j]=0;
}
void get_row(){
for(int i=1;i<=n;i++)
for(int j=m;j;j--)
if(a[i][j]) row[i][j]=row[i][j+1]+1;
else row[i][j]=0;
}
void get_line(){
int tmp,x;
for(int j=1;j<=m;j++){
top=0;
for(int i=1;i<=n;i++){
x=i;
while(top>0 && stk[top]>=row[i][j]){
tmp=min(stk[top],i-to[top]); tmp*=tmp;
ans1=max(ans1,tmp); tmp=stk[top]*(i-to[top]);
ans2=max(ans2,tmp); x=min(x,to[top]);
top--;
}
stk[++top]=row[i][j]; to[top]=x;
}
}
}
int main(){
n=rd(); m=rd();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=rd();
pre();
get_row(); get_line();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=!a[i][j];
get_row(); get_line();
printf("%d\n%d",ans1,ans2);
}
2、悬线法
神奇的悬线法!以前从来没听过诶,其实也很好懂
悬线法,形象地理解,就是在每个点上悬一条线qwq
悬线法的用途:针对求给定矩阵中满足某条件的极大矩阵,比如“面积最大的长方形、正方形”“周长最长的矩形等等”。可以满足在
时间复杂度为O(M*N)的要求,比一般的枚举高效的多,也易于理解。
引用别人博客的一些理论:
【最大子矩阵问题】
在一个给定的矩形中有一些障碍点,找出内部不包含障碍点的、轮廓与整个矩形平行或重合的最大子矩形。
【定义子矩形】
有效子矩形:内部不包含障碍点的、轮廓与整个矩形平行或重合的子矩形。
极大子矩形:每条边都不能向外扩展的有效子矩形。
最大子矩形:所有有效子矩形中最大的一个(或多个)。
那么这条线怎么悬呢?
从一个点(i,j)往上悬一条线,这条线是满足条件的最长的线,之后我们要悬着这根线左右移动,也就是求最大子矩阵的操作
http://blog.csdn.net/clover_hxy/article/details/50532289?locationNum=1&fps=1
(这个是一篇国家集训队论文《极大化思想解决最大子矩阵问题》)
算法实现:
设 h[i,j]为点(i,j)对应的悬线的长度。
l[i,j]为点(i,j)对应的悬线向左最多能够移动到的位置/长度。
r[i,j]为点(i,j)对应的悬线向右最多能够移动到的位置/长度。
我们可以一次预处理就能求出这几个数组,在处理问题的时候有一个模板
考虑递推的过程,从上往下,如果这里有悬线,那么它往左延伸的长度一定有一个最小值,我们可以一行一行取l的min(长度和位置略不同,这里用的是长度),同理取r的min,这之后,以这一行为结尾的一个最大长方形矩阵就是(h[i][j]+1)*(r[i][j]+l[i][j]+1) 这个式子根据自己数组定义的不同和处理方式的不同可能会不一样。如果是正方形矩阵只要把这两个值取min再乘方就好了
代码如下:
//悬线法
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define maxn 2005
using namespace std;
int n,m,a[maxn][maxn],ans1,ans2;
int h[maxn][maxn],r[maxn][maxn],l[maxn][maxn];
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
void pre(){
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(a[i][j]^a[i][j-1]) l[i][j]=l[i][j-1]+1;
if(a[i][m-j+1]^a[i][m-j+2]) r[i][m-j+1]=r[i][m-j+2]+1;
if(a[i][j]^a[i-1][j]) h[i][j]=h[i-1][j]+1;
}
}
int main(){
n=rd(); m=rd();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
a[i][j]=rd();
pre();
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(h[i][j]){
l[i][j]=min(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
}
int tmp=min(h[i][j]+1,(l[i][j]+r[i][j]+1));
ans1=max(ans1,tmp*tmp);
ans2=max(ans2,(h[i][j]+1)*(l[i][j]+r[i][j]+1));
}
printf("%d\n%d",ans1,ans2);
return 0;
}