* 1 准备知识
*1.1 几何学
* 2 数学模型
* 3 例题:放牧奶牛
* 4 格拉汉扫描法
* 5 凸包算法流程
* 6 伪代码
* 7 跟踪
* 8 问题提示
* 9 推广
* 10 例题
*10.1 树的难题 [IOI 1991, problem 2]
*10.2 吝啬的护城河建设
准备知识
几何学
数学模型
给出平面内的点集,找出面积最小的凸多边形,使得这些点在这个多边形之内(或者在它的边上)。
可以看出,多边形的顶点必须是给定点集中的点。
例题:放牧奶牛
农夫约翰想要修建一个栅栏,来防止讨厌的当地大学生在他的奶牛们睡觉的时候把它们掀翻。他的每头奶牛都有一个最喜欢的吃草点,农夫约翰想要把这些点都围在栅栏内。农夫约翰要围出一个凸的形状,因为这样更容易把奶牛赶进牧场。
帮助农夫约翰确定面积最小的而且包括所有奶牛喜爱的吃草点的栅栏形状。
格拉汉扫描法
解决二维凸包问题有好几种算法。这里,我们只介绍比较容易编码和记忆的“卷包裹”算法(其实就是我们所说的格拉汉扫描法——译者注)。
算 法的基本思想是在一个肯定会在凸包内的点周围不断地由顺时针或逆时针方向增加顶点,并确保每个内角都小于 180 度(保证最终答案是凸的)。如果三个连续的顶点构成的角度大于 180 度,删掉中间的点。可以用两个沿着多边形边的连续向量的叉积来判断角度是否大于 180 度。
凸包算法流程:
* 找出一个必定会在凸包内的中点
* 计算每个点和中点的连线与x轴的夹角(在 0——360 度的范围内)
* 根据这些夹角对顶点排序
* 加入最初的两个顶点
* 对于除最后一个顶点以外的其余顶点
* 让其成为凸包上的下一个顶点
* 检查它和前面两个顶点组成的角是否大于 180 度
*如果它和前面两个顶点组成的角大于 180 度,那么把它前面那个顶点删掉
* 加入最后一个顶点
*完成上述的删除任务
*检查最后一个顶点和它的前一个顶点和第一个顶点所组成的角是否大于 180 度,或者最后一个顶点和第一、第二个顶点组成的角是否大于 180 度。
*如果第一种情况为真,删除最后一个顶点,并且检查倒数第二个顶点。
*如果第二种情况为真,删除第一个顶点,继续检查。
*当两种情况都不为真时,停止。
*由于角度的计算方式,增加顶点的时间复杂度是线性的(就是我们所说的 O(n) )。因此,运行的时间复杂度决定于排序的时间复杂度,所以“卷包裹法”的时间复杂度是 O( n log n ),这是最优的。
简单点概括点:
1)求q中y坐标最小的点p0,若具有最小坐标的点有多个,则取最左边的点作为po.
2)对q中剩余的点按逆时针相对p0的极角排序,若有数个保留其中距p0最远的点
得到序列(p1,p2,...pn-1);
3)p0,p1,p2相继入栈
4)for i=3 to n-1 do
1) while 由次栈顶元素、栈顶元素和Pi所形成角不是向左转do栈顶元素出栈s;
2)pi入栈
5)打印按逆时针排列的栈中的各顶点。
代码:
program tubao;
const maxn=500;
type p=record
x,y:real;
end;
var n,top:integer;
list:array[0..maxn]of p;
s:array[1..maxn] of integer;
f:text;
procedure swap(var a,b:p);
var t:p;
begin t:=a;a:=b;b:=t end;
function m(p1,p2,p0:p):real;
begin
m:=(p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
end;
function comp(p1,p2:p):boolean;
var t:real;
begin
t:=m(p1,p2,list[0]);
if (t>0) or (t=0) and (sqr(p1.x-list[0].x)+sqr(p1.y-list[0].y)<
sqr(p2.x-list[0].x)+sqr(p2.y-list[0].y))
then comp:=true else comp:=false;
end;
procedure sort(l,r:integer);
var i,j:integer;
x:p;
begin
if r-l+1<=5 then begin
for j:=l+1 to r do
begin
i:=j;
while (i>1) and comp(list[i],list[i-1]) do
begin swap(list[i],list[i-1]); dec(i) end
end;
end else
begin
x:=list[l+random(r-l+1)];
i:=l;j:=r;
repeat
while comp(list[i],x) do inc(j);
while comp(x,list[j]) do dec(j);
if i<j then swap(list[i],list[j])
until i>=j;
sort(l,j);
sort(j+1,r);
end
end;
procedure init;
var i:integer;
begin
assign(f,'input.txt');
reset(f);
readln(f,n);
for i:=0 to n-1 do
begin
readln(f,list[i].x,list[i].y);
if (list[i].y<list[0].y) or
(list[i].y=list[0].y) and (list[i].x<list[0].x)
then swap(list[0],list[i]);
end ;
sort(1,n-1)
end;
procedure graham;
var i:integer;
begin
for i:=1 to 3 do s[i]:=i-1;
top:=3;
for i:=3 to n-1 do
begin
while m(list[i],list[s[top]],list[s[top-1]])>=0 do dec(top);
inc(top);
s[top]:=i;
end;
for i:=1 to top do
write('(',list[s[i]].x:7:2,',',list[s[i]].y:7:2,')');
writeln
end;
begin
init;
graham;
readln
end.
手动跟踪:
对于下面的跟踪,我们使用这些顶点:
选择一个中心,计算夹角,并排序。
现在,从加入最初的两个顶点开始。
现在,加入第三个顶点。由于这步操作没有和前两个顶点构成一个大于 180 度的角,我们只需加入这个顶点。
加入第四个顶点。这一次没有构成大于 180 度的角,所以不必做更多的工作。
加入第五个顶点。
由于第三个,第四个,和第五个顶点构成了一个大于 180 度的角(一个“向右的”转弯),我们删去第四个顶点。
第二个,第三个,和第四个顶点没有构成一个大于 180 度的角,所以我们完成了加入第五个顶点的任务。
加入第六个,第七个,和第八8个顶点。这个过程中没有附加的工作。
接着,加入最后一个顶点。
第八个,第九个,和第一个顶点构成“右转”;删除第九个顶点。
第七个,第八个,和第一个顶点已经完成了,可是第八个,第一个,和第二个顶点构成“右转”,所以我们必须删除第一个顶点。
现在,第八个,第二个,和第三个顶点构成“右转”,所以我们又要删除第二个顶点。
剩下的顶点并不违反“左转”原则,所以我们已经完成了任务,并且建立了给出顶点的凸包。
问题提示
类似把顶点放入多边形的题目通常是求凸包。如果题目要求一个面积最小的凸多边形,或者周长最小的凸多边形,那么我们几乎可以确定是要求凸包了。
推广
不幸的是,这个算法不能简单地推广到三维的情形。幸运的是,三维凸包算法全都超级复杂(四维以上的更恶心),所以题目不太可能要你去求。
如果你给多边形加上任何限制条件时,这个算法就玩完了(例如,多边形的顶点不多于 n 个,或者必须是矩形)。
例题
树的难题 [IOI 1991, problem 2]
已知:有一些树,你必须用铁丝围绕这些树,使得你用的铁丝最短。计算哪些树会在多边形的顶点上和铁丝的长度,还有农夫的小屋是在多边形内,还是在它之外,还是跨过多边形的边?
分析:多边形的顶点和铁丝的长度可以由问题直接得到。农夫的小屋是坐标轴上的一个矩形,你需要一点几何学知识来确定矩形中的所有点都在凸包中,或者都在凸包外,或者一些在凸包内,一些在凸包外。这样你就可以得到你想要的答案。查查几何手册,找一下这类问题的解法。
吝啬的护城河建设
已知:一些多边形房屋,计算包含这些多边形除去一个多边形的护城河的最小长度。
分析:计算已知多边形的凸包相当于计算它的所有顶点的凸包。这是一个组合问题,需要一个循环和一个凸包生成程序。对于每座房屋,删去这座房屋并计算剩下顶点的凸包。选择使得凸包最小的那座房屋。注意,只要考虑那些顶点和整个凸包重合的房屋即可,考虑整个凸包内的房屋对于问题没有任何帮助