【算法】最大子矩阵——非悬线法

问题引入

由于John建造了牛场围栏,激起了奶牛的愤怒,奶牛的产奶量急剧减少。为了讨好奶牛,John决定在牛场中建造一个大型浴场。但是John的奶牛有一个奇怪的习惯,每头奶牛都必须在牛场中的一个固定的位置产奶,而奶牛显然不能在浴场中产奶,于是,John希望所建造的浴场不覆盖这些产奶点。这回,他又要求助于Clevow了。你还能帮助Clevow吗?John的牛场和规划的浴场都是矩形。浴场要完全位于牛场之内,并且浴场的轮廓要与牛场的轮廓平行或者重合。浴场不能覆盖任何产奶点,但是产奶点可以位于浴场的轮廓上。Clevow当然希望浴场的面积尽可能大了,所以你的任务就是帮她计算浴场的最大面积。

对于百分之20的数据保证 0<=n<=100,1<=L,W<=1000

对于百分之百的数据保证 0<=n<=5000,1<=L,W<=30000

怎么做呢?悬线吗?不行,时空都炸了。
如果你只想拿部分分可以点击网页右上角的X
今天给各位大佬介绍一下非悬线法。

介绍及原理

首先明确一些概念。

1、定义有效子矩形为内部不包含任何障碍点且边界与坐标轴平行的子矩形。如图所示,第一个是有效子矩形(尽管边界上有障碍点),第二个不是有效子矩形(因为内部含有障碍点)。
在这里插入图片描述

2、极大有效子矩形:一个有效子矩形,如果不存在包含它且比它大的有效子矩形,就称这个有效子矩形为极大有效子矩形。(为了叙述方便,以下称为极大子矩形)

3、定义最大有效子矩形为所有有效子矩形中最大的一个(或多个)。以下简称为最大子矩形。

【定理1】在一个有障碍点的矩形中的最大子矩形一定是一个极大子矩形。
证明:如果最大子矩形A不是一个极大子矩形,那么根据极大子矩形的定义,存在一个包含A且比A更大的有效子矩形,这与“A是最大子矩形”矛盾,所以【定理1】成立。


算法的思路是这样的,先枚举极大子矩形的左边界,然后从左到右依次扫描每一个障碍点,并不断修改可行的上下边界,从而枚举出所有以这个定点为左边界的极大子矩形。考虑如图2中的三个点,现在我们要确定所有以1号点为左边界的极大矩形。先将1号点右边的点按横坐标排序。然后按从左到右的顺序依次扫描1号点右边的点,同时记录下当前的可行的上下边界。
开始时令当前的上下边界分别为整个矩形的上下边界。然后开始扫描。第一次遇到2号点,以2号点作为右边界,结合当前的上下边界,就得到一个极大子矩形(如图3)。同时,由于所求矩形不能包含2号点,且2号点在1号点的下方,所以需要修改当前的下边界,即以2号点的纵坐标作为新的下边界。第二次遇到3号点,这时以3号点的横坐标作为右边界又可以得到一个满足性质1的矩形(如图4)。类似的,需要相应地修改上边界。以此类推,如果这个点是在当前点(确定左边界的点)上方,则修改上边界;如果在下方,则修改下边界;如果处在同一行,则可中止搜索(因为后面的矩形面积都是0了)。由于已经在障碍点集合中增加了整个矩形右上角和右下角的两个点,所以不会遗漏右边界与整个矩形的右边重合的极大子矩形(如图5)。需要注意的是,如果扫描到的点不在当前的上下边界内,那么就不需要对这个点进行处理。
这样做是否将所有的极大子矩形都枚举过了呢?可以发现,这样做只考虑到了左边界覆盖一个点的矩形,因此我们还需要枚举左边界与整个矩形的左边界重合的情况。这还可以分为两类情况。一种是左边界与整个举行的左边界重合,而右边界覆盖了一个障碍点的情况,对于这种情况,可以用类似的方法从右到左扫描每一个点作为右边界的情况。另一种是左右边界均与整个矩形的左右边界重合的情况,对于这类情况我们可以在预处理中完成:先将所有点按纵坐标排序,然后可以得到以相邻两个点的纵坐标为上下边界,左右边界与整个矩形的左右边界重合的矩形,显然这样的矩形也是极大子矩形,因此也需要被枚举到。
通过前面两步,可以枚举出所有的极大子矩形。算法1的时间复杂度是 O ( S 2 ) O(S^2) O(S2)。这样,可以解决大多数最大子矩形和相关问题了。
图片:
图1:在这里插入图片描述

图2:在这里插入图片描述

图3:在这里插入图片描述

图4:在这里插入图片描述

图5:在这里插入图片描述

简述

所以我们可以把此算法精缩一下,及先把每一个障碍物当左边界,再枚举其右边的障碍物当右边界,取max为答案。
然后把每一个障碍物当右边界,再枚举其左边的障碍物当左边界,取max为答案。
最后判断一下以边界为边界的矩形,取max为答案。

代码实现

Code

#include<bits/stdc++.h>
#define int long long
using std::cin;
using std::cout;
int n,m,k,ans=-1e9,ax,ay,nx,ny;
struct fy
{
	int x,y;
}a[100000];
bool cmp1(fy x,fy y)
{
	if(x.x!=y.x)
		return x.x<y.x;
	return x.y<y.y;
}
bool cmp2(fy x,fy y)
{
	if(x.y!=y.y)
		return x.y<y.y;
	return x.x<y.x;
}
inline int max(int x,int y)
{
	return x>y?x:y;
}
inline int min(int x,int y)
{
	return x<y?x:y;
}
inline void init()
{
	std::ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
}
signed main()
{
	init();
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++)
		cin>>a[i].x>>a[i].y;
	a[k+1].x=a[k+1].y=a[k+2].x=a[k+3].y=0;
	a[k+3].x=a[k+4].x=n;
	a[k+2].y=a[k+4].y=m;
	k+=4;
	std::sort(a+1,a+k+1,cmp1);
	for(int i=1;i<=k;i++)
	{
		ax=a[i].x,ay=0,ny=m;
		for(int j=i+1;j<=k;j++)
		{
			nx=a[j].x;
			ans=max(ans,(nx-ax)*(ny-ay));
			if(a[j].y<a[i].y)
				ay=max(ay,a[j].y);
			else
				ny=min(ny,a[j].y);
		}
	}
	for(int i=k;i>=1;i--)
	{
		ax=a[i].x,ay=0,ny=m;
		for(int j=i-1;j>=1;j--)
		{
			nx=a[j].x;
			ans=max(ans,(nx-ax)*(ny-ay));
			if(a[j].y<a[i].y)
				ay=max(ay,a[j].y);
			else
				ny=min(ny,a[j].y);
		}
	}
	std::sort(a+1,a+k+1,cmp2);
	for(int i=1;i<k;i++)
		ans=max(ans,n*(a[i+1].y-a[i].y));
	cout<<ans;
}

总结

这种方法的时间复杂度是 O ( S 2 ) O(S^2) O(S2) 的,要和悬线法根据数据使用。
悬线法
本文有些内容从以下文章摘要:
浅谈用极大化思想解决最大子矩形问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值