Copyright (C) IOI日本委員会 1995. All rights reserved.


問題2の解答と解説

 線形リストと、ダイクストラ(E.W.Dijkstra)のアルゴリズムとして 知られている最短経路発見アルゴリズムに関する問題である。ただし、 ダイクストラのアルゴリズムについては問題文の中で詳しい説明があるし、 設問とは直接の関係がないので、ここでは触れない。

 設問 (a) は、本質的には2線分が交差するか否かを判定する問題である。 そのためには、まず線分の両端を対角線とする2つの長方形が共通部分を 持つかどうかを調べ(1番目のif文)、次にベクトルの外積を用いて 線分の交差を判定している(2番目、3番目の if 文)。

 原点を始点とする位置ベクトル a = (a1, a2) と b = (b1, b2) に対して、その 外積(の絶対値)とは a1*b2-a2*b1 なる量であり、この値が正のとき ベクトル a はベクトル b から見てその右側にあり、 負のときは左側にある。したがって、点 (a, b)、点 (c, d) を結ぶ線分と 点 (a', b')、点(c', d') を結ぶ線分が交差しているかどうかは、ベクトル (c-a, d-b) に関して2つのベクトル (a'-a, b'-b) と (c'-a, d'-b) が その両側にあるかどうかを調べればよい。
 もちろん、2直線の交点を求めてそれが線分の存在範囲に入っているか どうかを調べてもよい。

 設問 (b) は線形リストに関する問題である。問題文および関数(手続き) enter 冒頭のコメントにおいて、この線形リストはその要素の第3成分の 大きさによってソ−トされていることが明記されているので、 関数 get_min(BASIC では getMin)では、線形リストの先頭から 見ていって最初に path[ ].found が false となった要素を取り出せばよい ことがすぐわかる。この際、 それ以前の要素は線形リストからすべて削除する(削除しなくても 構わない)。

 問題文が長過ぎたせいもあろうが、問題1、問題2とも成績はあまり よくなかった。 どちらの問も、ほぼ満点といえる答案はごく僅かであった。


Cプログラム(1)

int visible(int i, int j)

#define cross_product(x1,y1,x2,y2) ((x1)*(y2)-(x2)*(y1))  
/* ベクトル(x1 y1)と(x2 y2)の外積 */

{
   int k,x1,y1,x2,y2,x3,y3,x4,y4;

   if (i==j) return TRUE;
   if ((P[j].x==0 || P[j].y==0 || P[j].x==100 || P[j].y==100)
       && P[j].x != P[j].y) return FALSE;

   x1 = P[i].x; y1 = P[i].y;              /* この点をAとする */
   x2 = P[j].x; y2 = P[j].y;              /*     B    */

   /* 線分ABとk番目の線分との交差を調べる */
   for (k=1; k<=N; k++) {  
       if ((i==2*k-1 && j==2*k) || (i==2*k && j==2*k-1)) continue;

       x3 = P[2*k-1].x; y3 = P[2*k-1].y;  /* この点をCとする */
       x4 = P[2*k].x;   y4 = P[2*k].y;    /*     D    */

       /* 以下では,線分ABと線分CDとが交差するか否か調べる */
       if (min(x1,x2)>max(x3,x4) || min(x3,x4)>max(x1,x2) ||
           min(y1,y2)>max(y3,y4) || min(y3,y4)>max(y1,y2)) continue;
       if ((long)cross_product(x3-x1,y3-y1,x2-x1,y2-y1)*
           (long)cross_product(x4-x1,y4-y1,x2-x1,y2-y1)>=0) continue;
       if ((long)cross_product(x1-x3,y1-y3,x4-x3,y4-y3)*
           (long)cross_product(x2-x3,y2-y3,x4-x3,y4-y3)>=0) continue;
       
       return FALSE;
   }
   return TRUE;
}

int get_min(int *from, int *to, float *distance)
{
   LIST currentPtr;

   currentPtr = sorted_list->next;
   while (currentPtr->next!=NULL && path[currentPtr->to].found)
      currentPtr = currentPtr->next;
   sorted_list->next = currentPtr;  /* これがなくても誤りではない */
   if (currentPtr->next==NULL) return EMPTY;
   else {
      *from = currentPtr->from; *to = currentPtr->to;
      *distance = currentPtr->distance;
      return !EMPTY;
   }
}


BASICプログラム(1)

'ベクトル(x1 y1)と(x2 y2)の外積を求める
FUNCTION crossProduct! (x1, y1, x2, y2)
   crossProduct = x1 * y2 - x2 * y1
END FUNCTION

FUNCTION max (x, y)
   IF x > y THEN max = x ELSE max = y
END FUNCTION

FUNCTION min (x, y)
   IF x < y THEN min = x ELSE min = y
END FUNCTION

FUNCTION visible (i, j)
   IF i = j THEN visible = true: EXIT FUNCTION
   IF (P(j).x=0 OR P(j).y=0 OR P(j).x=100 OR P(j).y=100) AND P(j).x<>P(j).y THEN
      visible = false: EXIT FUNCTION
   END IF

   x1 = P(i).x: y1 = P(i).y                      'この点をAとする
   x2 = P(j).x: y2 = P(j).y                      '    B   
   
   '線分ABとk番目の線分との交差を調べる
   FOR k = 1 TO N
       IF (i = 2*k-1 AND j = 2*k) OR (i = 2*k AND j = 2*k-1) THEN GOTO FORtail

       x3 = P(2 * k - 1).x: y3 = P(2 * k - 1).y  'この点をCとする
       x4 = P(2 * k).x: y4 = P(2 * k).y          '    D  

       '以下では,線分ABと線分CDとが交差するか否か調べる
       IF min(x1,x2) > max(x3,x4) OR min(x3,x4) > max(x1,x2) THEN GOTO FORtail
       IF min(y1,y2) > max(y3,y4) OR min(y3,y4) > max(y1,y2) THEN GOTO FORtail
       p1! = crossProduct!(x3 - x1, y3 - y1, x2 - x1, y2 - y1)
       p2! = crossProduct!(x4 - x1, y4 - y1, x2 - x1, y2 - y1)
       p3! = crossProduct!(x1 - x3, y1 - y3, x4 - x3, y4 - y3)
       p4! = crossProduct!(x2 - x3, y2 - y3, x4 - x3, y4 - y3)
       IF p1! * p2! >= 0 OR p3! * p4! >= 0 THEN GOTO FORtail
       
       visible = false: EXIT FUNCTION
FORtail: NEXT k
   visible = true
END FUNCTION
 

FUNCTION getMin (from, toward, d!)
   currentPtr = linearList(listHead).succ
   WHILE currentPtr <> endMark AND path(linearList(currentPtr).toward).found
      currentPtr = linearList(currentPtr).succ
   WEND
   listHead = currentPtr  'これがなくても誤りではない
   IF currentPtr = endMark THEN
      getMin = empty
   ELSE
      from = linearList(currentPtr).from: toward = linearList(currentPtr).toward
      d! = linearList(currentPtr).distance
      getMin = notEmpty
   END IF
END FUNCTION


Pascalプログラム(1)

function visible(i,j: integer): boolean;
   label LoopTail;
   var k,x1,y1,x2,y2,x3,y3,x4,y4: integer;

   function min(x,y: integer): integer;
   begin if x<y then min := x else min := y end;

   function max(x,y: integer): integer;
   begin if x>y then max := x else max := y end;

   function cross_product(x1,y1,x2,y2: integer): longint;
   { ベクトル(x1 y1)と(x2 y2)の外積 }
   begin cross_product := x1*y2-x2*y1 end;  

begin { visible }
   if i=j then begin visible := true; exit end;
   if ((P[j].x=0) or (P[j].y=0) or (P[j].x=100) or (P[j].y=100))
      and (P[j].x <> P[j].y) 
   then begin visible := false; exit end;

   x1 := P[i].x; y1 := P[i].y;              {この点をAとする}
   x2 := P[j].x; y2 := P[j].y;              {    B   }

   {線分ABとk番目の線分との交差を調べる}
   for k:=1 to N do begin
       if (i=2*k-1) and (j=2*k) or (i=2*k) and (j=2*k-1) then goto LoopTail;
       x3 := P[2*k-1].x; y3 := P[2*k-1].y;  {この点をCとする}
       x4 := P[2*k].x;   y4 := P[2*k].y;    {    D   }

       {以下では,線分ABと線分CDとが交差するか否か調べる}
       if (min(x1,x2)>max(x3,x4)) or (min(x3,x4)>max(x1,x2)) or
          (min(y1,y2)>max(y3,y4)) or (min(y3,y4)>max(y1,y2))
       then goto LoopTail;
       if cross_product(x3-x1,y3-y1,x2-x1,y2-y1)*
          cross_product(x4-x1,y4-y1,x2-x1,y2-y1)>=0
       then goto LoopTail;
       if cross_product(x1-x3,y1-y3,x4-x3,y4-y3)*
          cross_product(x2-x3,y2-y3,x4-x3,y4-y3)>=0
       then goto LoopTail;
       
       visible := false; exit;
   LoopTail:
   end;
   visible := true;
end; { visible }



function get_min(var from,toward: integer; var distance: real): boolean;
   var currentPtr: list;
begin
   currentPtr := sorted_list^.next;
   while (currentPtr<>nil) and path[currentPtr^.toward].found do 
      currentPtr := currentPtr^.next;
   sorted_list := currentPtr;  { これがなくても誤りではない }
   if currentPtr^.next=nil then get_min := empty
   else begin
      from := currentPtr^.from; toward := currentPtr^.toward; 
      distance := currentPtr^.distance;
      get_min := not empty;
   end;
end; { get_min }


JOIホームページへ戻る

JOI'95へ戻る