新的开始 第一章——悬线法解决最大子矩阵问题

新的开始 第一章 悬线法解决最大子矩阵问题

  • 参考文章
  • 悬线法
    • 最大子矩阵问题
    • 定义
    • 方法概述
    • 点疏
    • 点密
  • 例题
    • 算法1
    • 算法2

参考文章

  • 算法1
  • 算法2

悬线法

悬线法——解决最大子矩阵问题

最大子矩阵问题

在一个给定的矩形中有一些障碍点,找出内部不包含障碍点的、轮廓与整个矩形平行或重合的最大子矩形。

定义

1 悬线:悬线是一条以矩阵上边界障碍点为顶点的不经过障碍点的竖线
2 极大子矩形:内部不包含障碍点,四条边或贴合边界存在障碍点(无法再通过平移边扩大)的矩形

方法概述

由于最大子矩阵一定是某一个极大子矩阵,所以枚举极大子矩阵就可以找出最大子矩阵。但是极大子矩阵并不能被直接找到,于是该题就被转化成为了如何枚举极大子矩阵。具体如何使用悬线法有以下两种情况。

点疏

时间复杂度O(S^2)

  • 既然要枚举矩阵,我们可以通过枚举其边界来确认矩阵,但是粗略一算发现时间复杂度爆炸,思考其原因,发现我们在枚举边界的时候产生了大量的内部存在障碍点的子矩形,即使属于符合题目要求的子矩形,也有可能不是极大子矩形,那么如何利用边枚举极大有效子矩形呢?
  • 因为极大子矩阵的边界必须要存在障碍点或矩形边界,所以这里先将障碍点排序,然后从左到右枚举障碍点,当枚举到x号点,x号点便作为极大子矩形的左边界,上下边界为原矩形的上下边界,再加一层循环扫描x点后的每个点,统计以次点为右边界,当前上下边界为上下边界的极大子矩形面积,然后更新上下边界,再找下一个点,一直枚举即可。
  • 更新上下边界时,如果当前点在x号点的下方则需要更新下边界,在上方则需要更新上边界,在中间要分类讨论。
  • 仅这样做一遍会有遗漏,如边界和矩形边界重合的情况,解决方法是在预处理的时候加上边界的四个角作为障碍点,从右到左再扫一遍,搞定

点密

时间复杂度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(i1,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(Lefti1,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(Righti1,j,Right(i,j))

  • 然后就可以通过状态转移计算面积了,搞定

例题

算法1

[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;
}

算法2

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; 
}

你可能感兴趣的:(新的开始 第一章——悬线法解决最大子矩阵问题)