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

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}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值