三. Zj之XX洗浴
众所周知,这个就不扩展了……
自从这次被抓以后,zj同学很不服气。回家后,zj就开始了他的计划!(你不让我洗浴,我就在家洗个痛快!)zj打算在自家的院子里修一个洗浴池,当然他希望洗浴池越大越好。但是院子里有zj喜欢的一些植物,他不想毁掉任一颗植物,所以洗浴池不能将植物的位置占掉。Zj的院子和洗浴池都是矩形的,浴池要完全处在院子里,并且浴池的轮廓要与院子的轮廓平行或重合。每个植物可以看做一个点,浴池不能覆盖任何一个植物点,但是植物点可以在浴池的边上,请问zj的浴池最大能修多大.
输入格式:
输入文件的第一行包含两个整数l和w,分别表示院子的长和宽。文件的第二行包含一个整数n,表示植物的数量。以下n行每行包含两个整数x和y,表示一个植物点的坐标。所有植物点都位于院子内,即:0<=x<=l,0<=y<=w。
输出
输出文件仅一行,包含一个整数s,表示浴池的最大面积。
样例输入
10 10
4
1 1
9 1
1 9
9 9
样例输出
80
提示
各个测试点1s 0<=n<=5000, 1<=l,w<=30000
这是一个典型的求最大子矩阵问题,也就是那个求最大子正方形问题(状态压缩动规)的强化版。
最大子矩形问题:
在一个给定的矩形中有一些障碍点,要找出内部不包含任何障碍点的,轮廓与整个矩形平行或重合的最大子矩形。
定义有效子矩形为内部不包含任何障碍点的,边界与坐标轴平行的子矩形。
定义极大子矩形为每条边都不能向外扩展的有效子矩形。
定义最大子矩形为所有有效子矩形中最大的一个(或多个)。
在一个有障碍点的矩形中的最大子矩形一定是一个极大子矩形。 所以得出设计算法的思路:通过枚举所有的极大子矩形,找出最大子矩形。
针对问题的性质,可以设计出两个不同的算法。他们分别适用于不同的情况。
约定:为了叙述方便,设整个矩形的大小为N×M,其中障碍点个数为S。
第一个:从极大子矩形的性质入手。
极大子矩形的性质:
一个极大子矩形的每条边一定都不能向外扩展。更进一步地说,一个有效子矩形是极大子矩形的条件是这个子矩形的每条边要么覆盖了障碍点,要么与整个矩形的边界重合。
基本算法
算法:枚举上下左右四个边界,然后判断组成的矩形是否是有效子矩形。
复杂度:O(S5)
可以改进的地方:产生了大量的无效子矩形。
初步改进算法
算法:枚举左右边界,然后对处在边界内的点排序。每两个相邻的点和左右边界一起组成一个矩形。
复杂度:O(S3)
可以改进的地方:枚举了部分不是极大子矩形的情况。
设计算法的方向:
1、保证每一个枚举的子矩形都是有效的。
2、保证每一个枚举的子矩形都是极大的。
算法的过程:
枚举极大子矩形的左边界。
→ 根据确定的左边界,找出相关的极大子矩形。
→ 检查和处理遗漏的情况。
首先,将所有障碍点按横坐标从小到大的顺序将点标为1号点,2号点……
为了处理方便,在矩形的四个顶点上各增加1个障碍点。
第一次取1号点作为所要枚举的极大子矩形的左边界。
设定上下边界为矩形的上下边界。
从左向右扫描,第一次遇到2号点,可以得到一个有效的极大子矩形。
因为左边界覆盖1号点且右边界在2号点右边的有效子矩形都不能包含2号点,所以需要修改上下边界。
2号点在1号点上方,因此要修改上边界。
继续扫描到3号点,又得到一个极大有效子矩形。
因为3号点在1号点下方,所以要修改下边界。
以此类推,可以得到所有以1号点为左边界的极大有效子矩形。
然后将左边界移动到2号点、3号点……横坐标的位置。开始扫描以2号点、3号点……为左边界的极大子矩形。
前面的做法可以找出所有左边界覆盖了一个障碍点的极大子矩形,此外,还有两类遗漏的情况。
一类是左边界与整个矩形的左边界重合,右边界覆盖一个障碍点的情况。
解决方法:用类似的方法从右向左扫描一次。
另一类是左边界与整个矩形的左边界重合,且右边界也与整个矩形的右边界重合的情况。
解决方法:预处理时增加特殊判断。
时间复杂度为O(S2),空间复杂度为O(S)。
优点:利用了极大化思想,复杂度可以接受,编程实现简单。
缺点:使用有一定的局限性,不适合障碍点较密集的情况。
然后说一下第二个:因为算法1有使用的局限性,所以我们需要一种在障碍点很密集的时候仍能奏效的算法。
设计一种复杂度依赖于整个矩形面积的算法。
说明:如果整个矩形面积很大,可以通过离散化处理(本人觉得,这时候还是用第一个比较好)来优化。
定义:
有效竖线:除了两个端点外,不覆盖任何障碍点的竖直线段。
悬线:上端点覆盖了一个障碍点或达到整个矩形上端的有效竖线。
每个悬线都与它底部的点一一对应。
矩形中的每一个点(矩形顶部的点除外)都对应了一个悬线。
悬线的个数=(N-1)×M
如果把一个极大子矩形按x坐标不同切割成多个与y轴平行的线段,则其中至少存在一个悬线。
如果把一个悬线向左右两个方向尽可能移动,就能得到一个矩形,不妨称为这个悬线对应的矩形。
悬线对应的矩形不一定是极大子矩形,因为下边界可能还可以向下扩展。
原理:所有悬线对应矩形的集合一定包含了极大子矩形的集合。
通过枚举所有的悬线,找出所有的极大子矩形。
算法规模:
悬线个数=(N-1)×M
极大子矩形个数≤悬线个数
解决问题的关键:
对每个悬线的处理时间。
解决方法:
充分利用前面得到的信息。
具体方法:
设 H[i,j]为点(i,j)对应的悬线的长度。
L[i,j]为点(i,j)对应的悬线向左最多能够移动到的位置。
R[i,j]为点(i,j)对应的悬线向右最多能够移动到的位置。
如果(i-1,j)为障碍点,那么,如图所示,(i,j)对应的悬线长度1,左右能移动到的位置是整个矩形的左右边界。
即 H[i,j]=1,
L[i,j]=0,R[i,j]=m
如果(i-1,j)不是障碍点,那么,如图所示,(i,j)对应的悬线长度为(i-1,j)对应的悬线长度+1。
即 H[i,j]=H[i-1,j]+1
如果(i-1,j)不是障碍点,那么,如图所示,(i,j)对应的悬线左右能移动的位置要在(i-1,j)的基础上变化。
L[i,j]=max(L[i-1,j] , (i-1,j)左边第一个障碍点的位置)
同理,也可以得到R[i,j]的递推式
R[i,j]=min(R[i-1,j] , (i-1,j)右边第一个障碍点的位置)
时间复杂度为O(NM),空间复杂度为O(S)。
优点: 复杂度与障碍点个数没有直接关系。
缺点:障碍点少时处理较复杂,不如算法1
根据此题数据,这里的代码是算法一的(XMC)
然后,再说一下关于这一类题的推广。
最大权值子矩形问题
模型:在一个带权(正权)矩形中有一些障碍点,找出一个不包含障碍点的最大权值子矩形。
分析:在一个正权值的矩形中的最大权值子矩形一定是极大子矩形。所以,问题实际上可以依据极大化的思想,利用前面的方法解决。
最大子正方形问题(也可以状态压缩动规,需要的 Google )
模型:在一个矩形中存在S个障碍点,要求找出最大的不包含障碍点的正方形。
分析:在一个有障碍点的矩形中的最大有效子正方形一定是一个极大有效子正方形。
极大子正方形的性质:每一个极大子正方形都至少被一个极大子矩形包含,且这个极大子正方形一定有两条不相邻的边与包含它的极大子矩形的边重合。
解决方法:通过枚举每一个极大子矩形找出所有的极大子正方形。
每个极大子矩形对应的极大子正方形可能有多个,但大小都一样。
var
i,j,k,n,m,p:longint;
x,y:array[0..5010] of longint;
min,tmp,max:Longint;
ans,s1,s2:int64;
flag,flag2:boolean;
procedure init;
begin
readln(n,m);
readln(k);
for i:=1 to k do
readln(x[i],y[i]);
end;
procedure fast(l,r:Longint);
var
i,j:Longint;
t,tt:longint;
begin
i:=l;
j:=r;
t:=x[(i+j)>>1];
tt:=y[(i+j)>>1];
repeat
while (x[i]<t) or ((x[i]=t) and (y[i]<tt)) do inc(i);
while (x[j]>t) or ((x[j]=t) and (y[j]>tt)) do dec(j);
if i<=j then
begin
tmp:=x[i]; x[i]:=x[j]; x[j]:=tmp;
tmp:=y[i]; y[i]:=y[j]; y[j]:=tmp;
inc(i); dec(j);
end;
until i>j;
if i<r then fast(i,r);
if l<j then fast(l,j);
end;
procedure main;
begin
x[k+1]:=0;x[k+3]:=0;
y[k+1]:=0;y[k+3]:=m;
x[k+2]:=n;x[k+4]:=n;
y[k+2]:=m;y[k+4]:=0;
k:=k+2;
fast(1,k);
ans:=0;
i:=1;
while i<=k do
begin
min:=m;
max:=0;
j:=i+1;
flag:=true;
while (x[j]=x[i]) and (j<=k) do inc(j);
while j<=k do
begin
s1:=(x[j]-x[i])*(y[i]-max);
s2:=(x[j]-x[i])*(min-y[i]);
if flag then
begin if s1+s2>ans then ans:=s1+s2; end
else
begin
if s1>ans then ans:=s1;
if s2>ans then ans:=s2;
end;
if (y[j]<y[i]) and (y[j]>max) then max:=y[j];
if (y[j]>y[i]) and (y[j]<min) then min:=y[j];
if (y[j]=y[i]) then flag:=false;
//if (x[j]<>x[j-1]) and (not flag) then flag2:=false;
inc(j);
end;
inc(i);
end;
if k=0 then writeln(n*m) else
writeln(ans);
end;
begin
assign(input,'happy.in');reset(input);
assign(output,'happy.out');rewrite(output);
init;
main;
close(input);
close(output);
end.