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

 

巨人和鬼

一组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}

 

你可能感兴趣的:(算法,function,list,Integer,扩展,merge)