悬线法的用途
针对求给定矩阵中满足某条件的极大矩阵,比如“面积最大的长方形、正方形”“周长最长的矩形等等”。可以满足在时间复杂度为O(M*N)的要求,比一般的枚举高效的多,也易于理解。
悬线法思路
一般地,我们有一张n∗m的图,里面有一些障碍,我们想要求出一个最大的子矩形,使得它里面没有任何障碍(或者说,使它满足某个条件)。
我们考虑从每个点向上作一条射线,这条线如果遇到一个障碍或者是矩形的上边界就停下。这条线,就叫做悬线。我们把这条线尽可能地往左右移动(尽可能指的是不遇到障碍),就可以围成一个极大子矩形,这个子矩形是这条悬线所能构成的最大的子矩形。
显然,最大子矩形是属于所有悬线能构成的极大子矩形的集合里的。
于是,我们只要枚举每个悬线,O(1)地算出每个极大子矩形的面积,然后取一个max就行了。
考虑每一个点和每一条悬线一一对应,且一共有n∗m个点,所以复杂度就是O(nm)。
那么,问题就转化为如何O(1)地算出每个极大子矩形的面积。
先定义
Left[i][j]:代表从(i,j)能到达的最左位置
Right[i][j]:代表从(i,j)能到达的最右位置
Up[i][j]:代表从(i,j)向上扩展最长长度.
我们考虑递推,设Up[i][j]表示(i,j)的悬线长度。
Up[i][j] = Up[i - 1][j] + 1;
然后我们再考虑左右边界,记为Left[i][j],Right[i][j]进行递推。
Left[i][j] = max(Left[i - 1][j], Left[i][j])
Right[i][j] = min(Right[i - 1][j], Right[i][j])
最后,我们枚举每个点,统计一下答案。
算法图解
例题讲解
P1169棋盘制作
题意:
给你一个01棋盘,求其中01交错的最大正方形与矩形。
思路:
动态规划之悬线法
if语句执行的条件是a[i][j]!=a[i-1][j],即只有满足条件的情况下我们才能更改当前位置(i,j)的left数组与right数组,up数组.
而不满足条件时,我们当前位置(i,j)(i,j)的left,right,up数组并不会改变.
if(i>1&&a[i][j]!=a[i-1][j])
{
left[i][j]=max(left[i][j],left[i-1][j]);
right[i][j]=min(right[i][j],right[i-1][j]);
up[i][j]=up[i-1][j]+1;
}
注意点:
1.c++iostream中有一个left函数,所以请不要引用bits/stdc++.h或iostream
2.求正方形的面积就是长方形较短边长的平方
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=2500;
int Left[N][N],Right[N][N],Up[N][N],a[N][N];
int n,m,sz,sj;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
Up[i][j]=1;
Left[i][j]=Right[i][j]=j; // 初始化数组
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
{
if(a[i][j]!=a[i][j-1])
Left[i][j]=Left[i][j-1]; //预处理左边界,left初值,即(i,j)左的最大宽度
}
for(int i=1;i<=n;i++)
for(int j=m-1;j>0;j--)
{
if(a[i][j]!=a[i][j+1])
Right[i][j]=Right[i][j+1]; //预处理右边界,right初值,即(i,j)右的最大宽度
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(i>1&&a[i][j]!=a[i-1][j])
{
Left[i][j]=max(Left[i][j],Left[i-1][j]);
Right[i][j]=min(Right[i][j],Right[i-1][j]);
Up[i][j]=Up[i-1][j]+1;
}
int la=Right[i][j]-Left[i][j]+1; //横向长度
int lb=min(la,Up[i][j]); //竖向长度
sz=max(sz,lb*lb);
sj=max(sj,la*Up[i][j]);
}
cout<<sz<<endl<<sj<<endl;
}
题意:
求出能被“F”填满的最大矩形的面积,请输出最大矩形面积的三倍。
思路:
悬线法
设h(i,j)表示以(i,j)为下端点的悬线的最长长度。预处理l(i,j)和r(i,j)它们分别表示点(i,j)能扩展到的左边和右边的最近的障碍。
L(i,j)和R(i,j)分别表示使悬线有此长度的左边最近的障碍和右边最近的障碍。
答案即为max(h(i,j)*(R(i,j)-L(i,j)+1);对于本题,要求输出答案乘3后的值。
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=4100;
int l[N][N],r[N][N],u[N][N],a[N][N];
int n,m,t,s;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char ch;
cin>>ch;
if(ch=='F') a[i][j]=1;
l[i][j]=r[i][j]=j,u[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
{
if(a[i][j]&&a[i][j-1])
l[i][j]=l[i][j-1]; //预处理左边界,left初值,即(i,j)左的最大宽度
}
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
{
if(a[i][j]&&a[i][j+1])
r[i][j]=r[i][j+1]; //预处理右边界,right初值,即(i,j)右的最大宽度
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i][j])
{
if(i>1&&a[i-1][j])
{
l[i][j]=max(l[i][j],l[i-1][j]);
r[i][j]=min(r[i][j],r[i-1][j]);
u[i][j]=u[i-1][j]+1;
}
int d=r[i][j]-l[i][j]+1; //横向长度
s=max(d*u[i][j],s);}
}
cout<<s*3<<endl;
}
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=2000;
int a[N][N],l[N][N],r[N][N],u[N][N];
int n,m,la,lb;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
char ch;
cin>>ch;
if(ch=='1') a[i][j]=1;
l[i][j]=r[i][j]=j,u[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=2;j<=m;j++)
{
if(a[i][j]&&a[i][j-1])
l[i][j]=l[i][j-1];
}
for(int i=1;i<=n;i++)
for(int j=m-1;j>=1;j--)
{
if(a[i][j]&&a[i][j+1])
r[i][j]=r[i][j+1];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(a[i][j])
{
if(i>1&&a[i-1][j])
{
l[i][j]=max(l[i-1][j],l[i][j]);
r[i][j]=min(r[i-1][j],r[i][j]);
u[i][j]=u[i-1][j]+1;
}
la=r[i][j]-l[i][j]+1;
la=min(la,u[i][j]);
lb=max(lb,la);
}
}
cout<<lb<<endl;
}
P2701 巨大的牛棚
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N=2200;
int a[N][N],l[N][N],r[N][N],u[N][N];
int n,t,la,lb;
int main()
{
cin>>n>>t;
memset(a,1,sizeof a);
for(int i=1;i<=t;i++)
{
int x,y;
cin>>x>>y;
a[x][y]=0;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
l[i][j]=r[i][j]=j;
u[i][j]=1;
}
for(int i=1;i<=n;i++)
for(int j=2;j<=n;j++)
{
if(a[i][j]&&a[i][j-1])
l[i][j]=l[i][j-1];
}
for(int i=1;i<=n;i++)
for(int j=n-1;j>=1;j--)
{
if(a[i][j]&&a[i][j+1])
r[i][j]=r[i][j+1];
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(a[i][j])
{
if(i>1&&a[i-1][j])
{
l[i][j]=max(l[i-1][j],l[i][j]);
r[i][j]=min(r[i-1][j],r[i][j]);
u[i][j]=u[i-1][j]+1;
}
la=r[i][j]-l[i][j]+1;
la=min(la,u[i][j]);
lb=max(la,lb);
}
}
cout<<lb<<endl;
}
如果觉得写的不错,点个赞吧^ ^