题目描述
由于John建造了牛场围栏,激起了奶牛的愤怒,奶牛的产奶量急剧减少。为了讨好奶牛,John决定在牛场中建造一个大型浴场。但是John的奶牛有一个奇怪的习惯,每头奶牛都必须在牛场中的一个固定的位置产奶,而奶牛显然不能在浴场中产奶,于是,John希望所建造的浴场不覆盖这些产奶点。这回,他又要求助于Clevow了。你还能帮助Clevow吗?
John的牛场和规划的浴场都是矩形。浴场要完全位于牛场之内,并且浴场的轮廓要与牛场的轮廓平行或者重合。浴场不能覆盖任何产奶点,但是产奶点可以位于浴场的轮廓上。
Clevow当然希望浴场的面积尽可能大了,所以你的任务就是帮她计算浴场的最大面积。
输入输出格式
输入格式:
输入文件的第一行包含两个整数L和W,分别表示牛场的长和宽。文件的第二行包含一个整数n,表示产奶点的数量。以下n行每行包含两个整数x和y,表示一个产奶点的坐标。所有产奶点都位于牛场内,即:0<=x<=L,0<=y<=W。
输出格式:
输出文件仅一行,包含一个整数S,表示浴场的最大面积。
输入输出样例
输入样例#1:
10 10
4
1 1
9 1
1 9
9 9
输出样例#1:
80
说明
0<=n<=5000
1<=L,W<=30000
思路
超级经典的题目。
题目的数学模型就是给出一个矩形和矩形中的一些障碍点,要求出矩形内的最大有效子矩形。
一看就是最大子矩阵面积,但是怎么求呢?
可以想到的是,这个矩阵一定是极大子矩阵。(什么?你不知道什么是极大子矩阵?百度吧。。。)
然后怎么求呢?
这个子矩阵每一条边都不可以伸展了,也就是说他的边界上一定有障碍点或者是与边界重合了。
算法一:
直接想到的是,上下左右枚举四个边界,再看看是不是满足条件。
复杂度?
O(n5)
原因是什么?我们枚举了很多没有用的矩阵。
很明显,我们不可以使用这个算法。
算法二
那么,我们可不可以优化一下?
我们只枚举左右边界,然后对范围内的点排序。每两个相邻的点加上左右边界就是目前的矩阵。
复杂度?
O(n3)
还是过不了啊。。。
原因何在?毕竟还是有很多的矩阵不是极大子矩阵。
说了这么多,本质上都没有用到极大子矩阵这个性质。现在我们就要用用了。
想想怎么优化。
我们要保证以下几点:
所有枚举的矩阵都是有效的。
所有枚举的矩阵都是极大的。
算法三
首先我们把点从左到右标号。为了方便处理,我们把整个矩阵的四个角都变成障碍点。
然后把点从左到右排序。
第一次取1号点作为左边界,暂定整个矩阵的上下边界是目前上下边界。然后从左往右扫,遇到一个点,就修改上下边界。这样我们就得到所有的以1号点为左边界的极大有效子矩阵。
然后再是2号点,3号点……一直到 (n−1) 号点。
但是这样不完整。
为什么?还有右边界啊,你怎么保证右边界也这样呢?
所以按照同样的道理从右向左再来一次。
当然,如果左右边界不是排序后的第一个点的横坐标而是整个矩形的边界,那就需要特判了。
复杂度?
O(n2)
这就可以接受了。
但是,如过障碍点太密集,这个算法就不太优了。
算法四
考虑悬线法
什么是悬线?我们引进“有效竖线”。
有效竖线就是除了两个端点,不覆盖任何障碍点的竖线段。
于是,悬线就是上端点是一个障碍点或者是上端点是整个矩阵的边界的有效竖线。
这样的悬线有 (n−1)∗m个.
如果把一个极大子矩阵按照横坐标切成一条一条的,那么其中至少有一个悬线。
我们把一根悬线左右乱移动到尽可能最偏,这样左右边界就确定了。
还有上下边界
考虑点i与点i-1。
如果点i-1是障碍点,那么高度是1
如果不是,那么就是上一个高度+1。
这样就确定矩阵了。
复杂度?
O(n∗m)
也很清真啊,关键是不受到障碍点个数的影响。
当然,障碍点少的话,离散化一下也很好。
代码
详见批注
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N=5000+5;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int L,W,n,up,down,l,ans=0;
struct data
{
int x,y;
}cow[N];
int cmp1(data a,data b)
{
if (a.x==b.x) return a.y<b.y;
return a.x<b.x;
}//先按照横坐标排序
int cmp2(data a,data b)
{return a.y<b.y;}//按照纵坐标排序
int main()
{
L=read();W=read();n=read();
for (int i=1;i<=n;i++)
{cow[i].x=read();cow[i].y=read();}
cow[++n].x=0,cow[n].y=0;
cow[++n].x=L,cow[n].y=0;
cow[++n].x=0,cow[n].y=W;
cow[++n].x=L,cow[n].y=W;//四个端点也作为障碍
sort(cow+1,cow+1+n,cmp1);
for (int i=1;i<=n;i++)
{//悬线枚举
up=W,down=0,l=L-cow[i].x;//初始化
for (int j=i+1;j<=n;j++)
{//从i往右扫描
if (cow[j].y<=up&&cow[j].y>=down)//在悬线的范围内
{
if (l*(up-down)<=ans) break;//剪枝
ans=max(ans,(cow[j].x-cow[i].x)*(up-down));//更新面积
if (cow[j].y==cow[i].y) break;
else if (cow[j].y>cow[i].y) up=min(up,cow[j].y);
else down=max(down,cow[j].y);//更新上下边界
}
}
up=W,down=0,l=cow[i].x;//再次初始化
for (int j=i-1;j>0;j++)
{//从i往左扫描
if (cow[j].y<=up&&cow[j].y>=down)
{
if (l*(up-down)<=ans) break;
ans=max(ans,(cow[i].x-cow[j].x)*(up-down));
if (cow[j].y==cow[i].y) break;
else if (cow[j].y>cow[i].y) up=min(up,cow[j].y);
else down=max(down,cow[j].y);//更新上下边界
}
}
}
sort(cow+1,cow+1+n,cmp2);
for (int i=1;i<n;i++)
ans=max(ans,cow[i+1].y-cow[i].y);
printf("%d\n",ans);
return 0;
}