您的位置:首页 > 其它

凸包问题扩展 巨人和鬼 分治+递归

2014-10-29 18:31 633 查看
A group of n Ghostbusters is battling n ghosts. Each Ghostbuster is armed

with a proton pack, which shoots a stream at a ghost, eradicating it. A

stream goes in a straight line and terminates when it hits the ghost. The

Ghostbusters decide upon the following strategy. They will pair off with the

ghosts, forming n Ghostbuster-ghost pairs, and then simultaneously each

Ghostbuster will shoot a stream at his chosen ghost. As we all know, it is

very dangerous to let streams cross, and so the Ghostbusters must choose

pairings for which no streams will cross. Assume that the position of each

Ghostbuster and each ghost is a fixed point in the plane and that no three

positions are collinear.

• Argue that there exists a line passing through one Ghostbuster and

one ghost such the number of Ghostbusters on one side of the line

equals the number of ghosts on the same side. Describe how to find

such a line in O(nlogn) time.

• Give an O(n 2 logn)-time algorithm to pair Ghostbusters with ghosts

in such a way that no streams cross.

巨人和鬼

一组n个巨人正与n个鬼进行战斗,每个巨人的武器是一个质子炮, 它可以把一串质子流射中鬼而把鬼消灭。质子流沿直线行进,在击中鬼时就终止。巨人决定采取下述策略。他们寻找鬼配对,以形成n个巨人─鬼对,。然后每个巨人同时向他选取的鬼射出一串质子流。我们知道,让质子流互相交叉是很危险的。因此巨人选择的配对方式应该使质子流都不会交叉。假定每个巨人和每个鬼的位置都是平面上的一个固定点,并且没有三个位置共线, 求一种配对方案。

自己分析:采用分治方法,寻找中间界限,将大区间问题分成左右2个子区间,并同理递归求解。可能有很多种配对的方法,但只需找一种即可。此种方法必能找一种,因为每次找的都是成立的,能保证左右两边都能找到配对的。

算法分析:

我们设P1..Pn为巨人的固定点;Pn+1..P2n为鬼的固定点。我们采取分治采取分治策略寻找序列[Pp..Pr]中的配对方案(初始时[Pp..Pr]为[P1..P2n]):

在[Pp..Pr]中找出一个最低位置(Y坐标值最小)的一个点P0,如果这样的点有多个,则选取最左边的点为P0,P0与Pp交换。然后将其余点[Pp+1..Pr]按相对 Pp的极角递增的顺序排列。显然Pp与其余点Pp+1..Pr之间的任何线段是不会交叉的。我们从Pp开始寻找一个巨人和鬼成对的最小子区间[Pp..Pi](p≤i≤r)。若该子区间仅剩一个元素,配对结束;否则巨人(鬼)Pp与鬼(巨人)Pi配对。这样使得尚未配对的巨人和鬼分布在两个子区间[Pp+1..Pi-1],[Pi+1..Pr]。继续按上述分治策略分别递归求解[Pp+1..Pi-1]和[Pi+1..Pr]。



如上图,以点P1,将其他点按相对P1的极角递增排序。然后从P2开始顺序地找一个最短的配对序列P1-P6(鬼和巨人的个数要相等,这样才能一一配对。P1-P6是3个鬼,3个巨人)。怎样求分割线P1P6呢?是给鬼和巨人一个标志,设鬼为-1,巨人为1,从P2开始找时,逐渐累加,直到为1时停止,表明鬼和人的个数相等,如P1到P2时:2个鬼(-1-1=-2),继续到P3:2鬼1巨人(-1-1+1=-1),P4:3鬼1巨人(-1-1+1-1=-2),P5:3鬼2巨人(-1-1+1-1+1=-1),P6:3鬼3巨人(-1-1+1-1+1+1=0),此时鬼和巨人的个数相等,则分割线为P1P6,将P1-P8分割成(P2-P5)和(P7-P8)。再递归对(P2-P5)和(P7-P8)按同样的方法分治求解。

上面求分割线P1P6的参考代码如下:

m = List[p].k; i = p;//其中p为区间[p,r]的起点,鬼的k=-1,巨人的k=1.m为巨人、鬼个数累积和

{求巨人和魔鬼成对的最小子区间list[p..i]}

While ( m != 0)//当m=0时,表明找到了一个最短的鬼和巨人个数相等(即可配对)的子区间

{ ++i;

m += List[i].k;//k不断累加,从p到最终满足条件的i

}

下面是程序题解(摘自《ACM程序设计培训教程 吴昊》第15章 凸包问题中的案例2 巨人和鬼P232):

Program Giants_And_Monsters;

Const

Maxn = 100;

Type

Node = Record

k : Integer; {k=1:巨人;k=-1:魔鬼}

x, y : Real{坐标}

End;

Var

N, i : Integer;{魔鬼和巨人的对数,辅助变量}

F : Text;{文件变量}

List, Lt : Array [1..2 * Maxn] of Node;{点集,辅助点集}

p0 : Node;{最低位置点}

Function Comp(Var p1, p2 : Node):Boolean;

{计算(P1-P0)*(P2-P0)的叉积值。若值为正(相对于P0来说,P2的极角大于P1的极角 ) 返回true;否则返回false}

Begin

If (p1.x-p0.x)*(p2.y-p0.y) - (p1.y-p0.y)*(p2.x-p0.x) > 0

Then Comp := True

Else Comp := False

End;

Procedure Merge(p, q, r : Integer);

{将两个已按极角递增顺序排好序的子序列list[p..q]和list[q+1..r]合并排序成一个序列list[p..r]}

Var

i, j, t : Integer;

Begin

t := p; i := p; j := q + 1;

While t <= r Do Begin

If (i <= q) And ((j > r) Or Comp(List[i], List[j])) Then

Begin

Lt[t] := List[i]; Inc(i)

End

Else Begin

Lt[t] := List[j]; Inc(j)

End;{else}

Inc(t)

End;{while}

For i := p to r Do List[i] := Lt[i]

End;{merge}

Procedure Merge_Sort(p, r : Integer);

Var

q : Integer;

Begin

If p <> r Then Begin

q := (p + r - 1) div 2;{计算中间下标q}

Merge_Sort(p, q);{对子序列list[p..q]递归排序}

Merge_Sort(q + 1, r);{对子序列list[q+1..r]递归排序}

Merge(p, q, r){合并两个排好序的子序列}

End{then}

End;{merge_sort}

Procedure Swap(Var a, b : Node);{交换a和b两点}

Var

t : Node;

Begin

t := a; a := b; b := t

End;

Procedure Out_pos(Var p : Node);{输出p点的X和Y坐标}

Begin

Write(p.x:8:2, p.y:8:2)

End;

Procedure Pick(p, r : Integer);

Var

i, m : Integer;

Begin

If p < r Then Begin

m := p;{求出Y坐标值最小的点或具有Y最小值的数个点中最左边的点m}

For i := p to r Do

If (List[i].y < List[m].y) Or (List[i].y = List[m].y)

And (List[i].x < List[m].x) Then m := i;

Swap(List[p], List[m]);{m点与p点交换并设为P0}

p0 := List[p];

Merge_Sort(p + 1, r);{对list[p+1..r]按极角递增的顺序排序}

m := List[p].k; i := p;

{求巨人和魔鬼成对的最小子区间list[p..i]}

Repeat

Inc(i);

m := m + List[i].k

Until m = 0;

Out_pos(List[p]);{list[p]和list[i]配对}

Out_Pos(List[i]);

Writeln;

Pick(p+1, i - 1);{递归搜索子序列list[p+1..i-1]中配对情况}

Pick(i + 1, r){递归搜索子序列list[i+1..r]中配对情况}

End{then}

End;{pick}

Begin

Assign(F, 'INPUT.DAT');{输入文件名串与文件变量连接}

Reset(F);{文件读准备}

Readln(F, N);{读入巨人和鬼的对数}

For i := 1 to N Do Begin{读入N个巨人的位置}

Readln(F, List[i].x, List[i].y);

List[i].k := 1

End;

For i := N + 1 to 2 * N Do Begin{读入N个魔鬼的位置}

Readln(F, List[i].x, List[i].y);

List[i].k := -1

End;

Pick(1, 2 * N)

End.{main}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: