悬线法——解决最大子矩阵问题
在一个给定的矩形中有一些障碍点,找出内部不包含障碍点的、轮廓与整个矩形平行或重合的最大子矩形。
1 悬线:悬线是一条以矩阵上边界或障碍点为顶点的不经过障碍点的竖线
2 极大子矩形:内部不包含障碍点,四条边或贴合边界或存在障碍点(无法再通过平移边扩大)的矩形
由于最大子矩阵一定是某一个极大子矩阵,所以枚举极大子矩阵就可以找出最大子矩阵。但是极大子矩阵并不能被直接找到,于是该题就被转化成为了如何枚举极大子矩阵。具体如何使用悬线法有以下两种情况。
时间复杂度O(S^2)
时间复杂度O(n*m)
这里用枚举矩阵内所有点的方法,由悬线的定义可知,每一个点作为悬线的下端点时(障碍点也可以做悬线的下端点)都对应了一条且仅有一条悬线,将这条悬线向左边、右边平移,直到遇到障碍点或边界,必能扫描出一个矩形。而这所有矩形的集合必然包涵了极大子矩阵的集合,同时包含了最大子矩阵。
假设我们计算子矩阵的大小时间复杂度为O(1),那么这个算法的时间复杂度就取决于完整矩阵的边长O(m*n),现在问题再次转化,转化为如何最快地确认当前扫描的子矩阵的大小
当我们发现并不能够单纯地O(1)计算时,考虑状态转移,发现如果当前点正上方的一个点不是障碍点,那么悬线长度为上一条加一,否则为1
而左右能够到达的最大位置通过比较原位置和更新高度后新的一行的障碍点来修改,这里通过之前的理论提出定义和公式
定义
H e i g h t ( i , j ) Height_{(i,j)} Height(i,j) :以(i,j)为下端点的悬线的高
L e f t ( i , j ) Left_{(i,j)} Left(i,j) :悬线(i,j)能到达的左边界
R i g h t ( i , j ) Right_{(i,j)} Right(i,j):悬线(i,j)能到达的左边界
初始化
H e i g h t ( i , j ) = 1 Height_{(i,j)} =1 Height(i,j)=1
L e f t ( i , j ) = j Left_{(i,j)} =j Left(i,j)=j
R i g h t ( i , j ) = j Right_{(i,j)} =j Right(i,j)=j
左右边界任然需要按照题目要求初始化
递推式
H e i g h t ( i , j ) = H e i g h t ( i − 1 , j ) + 1 Height_{(i,j)} =Height_{(i-1,j)}+1 Height(i,j)=Height(i−1,j)+1
L e f t ( i , j ) = m a x ( L e f t i − 1 , j , L e f t ( i , j ) ) Left_{(i,j)} =max(Left_{i-1,j},Left_{(i,j)}) Left(i,j)=max(Lefti−1,j,Left(i,j))
R i g h t ( i , j ) = m i n ( R i g h t i − 1 , j , R i g h t ( i , j ) ) Right_{(i,j)} =min(Right_{i-1,j},Right_{(i,j)}) Right(i,j)=min(Righti−1,j,Right(i,j))
然后就可以通过状态转移计算面积了,搞定
[vijos1055] 奶牛浴场
AC代码(丑):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LL long long
using namespace std;
struct point
{
int x,y;
} p[5005];
bool cmp(point a,point b)
{
return a.x<b.x;
}
bool cmp3(point a,point b)
{
return a.y<b.y;
}
int main()
{
int l,w,n,s=0;
scanf("%d%d%d",&l,&w,&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&p[i].y,&p[i].x);
if(p[i].y<=0) p[i].y=0;
if(p[i].x<=0) p[i].x=0;
if(p[i].y>l) p[i].y=l;
if(p[i].x>w) p[i].x=w;
}
p[n+1].y=0,p[n+1].x=1,n++;
p[n+1].y=l,p[n+1].x=1,n++;
p[n+1].y=0,p[n+1].x=w,n++;
p[n+1].y=l,p[n+1].x=w,n++;
sort(p+1,p+n+1,cmp3);
for(int i=2;i<=n;i++)
s=max(s,(p[i].y-p[i-1].y)*l);
sort(p+1,p+n+1,cmp);
for(int i=1;i<=n-1;i++)
{
int h1=0,h2=l;
for(int j=i+1;j<=n;j++)
{
if(p[i].x==p[j].x)
continue;
s=max(s,(h2-h1)*(p[j].x-p[i].x));
if(p[j].y<h2&&p[j].y>h1)
{
if(p[j].y<p[i].y)
h1=p[j].y;
else if(p[j].y>p[i].y)
h2=p[j].y;
else
{
h1=p[j].y;
}
}
}
}
for(int i=1;i<=n-1;i++)
{
int h1=0,h2=l;
for(int j=i+1;j<=n;j++)
{
if(p[i].x==p[j].x)
continue;
s=max(s,(h2-h1)*(p[j].x-p[i].x));
if(p[j].y<h2&&p[j].y>h1)
{
if(p[j].y<p[i].y)
h1=p[j].y;
else if(p[j].y>p[i].y)
h2=p[j].y;
else
{
h2=p[j].y;
}
}
}
}
cout<<s<<endl;
}
P1169 [ZJOI2007] 棋盘制作
AC代码(初始化写错查了很久,真蠢):
#include
using namespace std;
int mp[2005][2005];
int lft[2005][2005],rit[2005][2005],height[2005][2005];
int main()
{
int n,m,ans1=0,ans2=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%d",&mp[i][j]);
lft[i][j]=j,rit[i][j]=j;
height[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
if(mp[i][j]!=mp[i][j-1])
lft[i][j]=lft[i][j-1];
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
if(mp[i][j]!=mp[i][j+1])
rit[i][j]=rit[i][j+1];
for(int i=2;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(mp[i][j]!=mp[i-1][j])
{
lft[i][j]=max(lft[i][j],lft[i-1][j]);
rit[i][j]=min(rit[i][j],rit[i-1][j]);
height[i][j]=height[i-1][j]+1;
}
int h=height[i][j];
int l=rit[i][j]-lft[i][j]+1;
ans1=max(ans1,min(l,h)*min(l,h));
ans2=max(ans2,l*h);
}
cout<<ans1<<"\n"<<ans2<<endl;
}