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


問題2 解答・解説

 以下に示すプログラムは解答の一例である。この例解は問題文の指定された 位置にそのまま埋め込めば正しく動作するようなプログラムとして書いてあるが、 大域的変数や大域的配列を用い、その宣言や関数のプロトタイプ宣言などを プログラムの冒頭に置くような形のプログラムでもよい。

 アルゴリズムは、プログラムから容易に読み取れるであろう。 タイルが張られている状況を表すために2次元配列 bathroom を用意し、 junjo に従って実際にタイルを張っていく。タイル1枚毎に、きちんと 張れるか否かを関数(手続き、プロシージャ) possible_to_put (BASICでは possibleToPut) で判定する。 k 番目のタイルが風呂場からはみ出ることなく張れるかどうかを チェックするためには、風呂場を表す配列を1周り大きく取って風呂場の 南側縁と東側縁に当たるところに最初からタイルを張っておき、 当該タオルが張れるかどうかはタイルを張るべき左上の位置から 右(東)側に junjo[k]+1 区画かつ下(南)側にも junjo[k]+1 区画(これは C の場合。BASIC では junjo(k)、 Pascal では junjo[k])の正方形の範囲にまだタイルが張られて いないことを調べればよい。
 このように、境界に置いて範囲をはみ出たか否かをチェックする役割を 持たせたものを「番兵」という。もちろん、番兵は使わなくても出来るが、 番兵を使うことによって、「風呂場をはみ出たか否か」と 「タイルが重なっているか否か」を別の場合として区別することなく 扱えるので、プログラムが単純になる。

 次の3点に注意したい(C言語を例にして説明するが、他の言語でも 同様である)。
 (1)関数 check() において、

    for (k=0; k <= N; k++) {
        for (i=0; i < SIZE; i++) {
            for (j=0; j < SIZE; j++) if (bathroom[i][j]==0) ...
        }
    }
    for (k=0; k <= N; k++) {
        for (i=0; i < SIZE-junjo[k]; i++) {
            for (j=0; j < SIZE-junjo[k]; j++) if (bathroom[i][j]==0) ...
        }
    }
とするのは誤りである。 junjo で与えられる順序に従って k 番目に与えられたタイルは、風呂場でまだタイルが張られていない最も 左上の位置に必ず張らなければならない(張ることができるかどうかを 調べたい)のであるから、その位置の右側あるいは下側に k 番目 のタイルを張れるほど余地がないからといってその位置を飛ばして しまってはならない(飛ばしてしまった位置には k+1 番目以降に もっと小さいタイルを張ることができてしまうかもしれないので、 実際の張り方の数より多い誤った答が得られる可能性がある)。
 解答例では風呂場の周囲のうち東側と南側に番兵として最初から1を 書き込んでおく方法を用いているが、このように番兵を置く方法は、 上述のようにプログラムを単純化するだけでなく、タイルの形が複雑に なった場合や風呂場にタイルの置けない場所をいろいろ設定した場合など への拡張も容易である。
 (2) possible_to_put において、最初のfor文の前に
  if (i+k < SIZE || j+k < SIZE) return 0;
を置くことによってタイルが風呂場内に収まっているかどうかを チェックするだけでは不十分である。これでは風呂場内に収まるか どうかが分かるだけであり、右または下側にすでにタイルが張られて いるために当該タイルを張るだけの余地がないことがあるかもしれない からである。
 (3) possible_to_put において、
    for (I=i; I < i+k; I++) if (bathroom[I][j]==1) return 0;
    for (J=j; J < j+k; J++) if (bathroom[i][J]==1) return 0;
    for (I=i; I < i+k; I++)
       for (J=j; J < j+k; J++) if (bathroom[i][J]==1) return 0;
とする必要はない。前者の方が効率がよい。
 なお、break(EXIT)文を使えばgoto文の使用は避けることも出来るが、 むしろその方が分かりやすいだろうと思い、解答例ではあえて goto文を使った。


Turbo Cプログラム

#define SIZE 6  /* 風呂場のサイズ */

int possible_to_put(int i, int j, int k, int bathroom[][SIZE+1])
/* 大きさk×kのタイルの左上頂点をbathroom[i][j]にして置けるか否か判定する */
{
   int I,J;

   for (I=i; I < i+k; I++) if (bathroom[I][j]==1) return 0;
   for (J=j; J < j+k; J++) if (bathroom[i][J]==1) return 0;

   for (I=i; I < i+k; I++)
      for (J=j; J < j+k; J++) bathroom[I][J] = 1;
   return 1;
}


int check(int junjo[], int N)
/* junjoで与えられるタイル順列のもとで、風呂場の左上を優先して
   タイルを張っていけるか否かを判定し、yesなら1を、noなら0を返す */
{
   int i,j,k,
       bathroom[SIZE+1][SIZE+1]; /* 風呂場にタイルが張られている状況 */

   for (i=0; i < =SIZE; i++)
      for (j=0; j < =SIZE; j++) {
         if (i < SIZE && j < SIZE) bathroom[i][j] = 0;  /* 風呂場内 */
         else bathroom[i][j] = 1;  /* 番兵 */
      }

   for (k=0; k < =N; k++) {
      for (i=0; i < SIZE; i++) 
         for (j=0; j < SIZE; j++) if (bathroom[i][j]==0) goto TRY;
      return 0;
      TRY: if (!possible_to_put(i,j,junjo[k]+1,bathroom)) return 0;
   }
   return 1;
}


Quick BASICプログラム

FUNCTION check(junjo(),n)
'junjoで与えられるタイル順列のもとで、風呂場の左上を優先して
'タイルを張っていけるか否かを判定し、yesなら1を、noなら0を返す

   DIM bathroom(size+1,size+1)  ' 風呂場にタイルが張られている状況を表わす

   FOR i=1 TO size: FOR j=1 TO size: bathroom(i,j)=0: NEXT j: NEXT i
   FOR i=1 TO size+1: bathroom(i,size+1)=1: NEXT i  ' 番兵
   FOR j=1 TO size+1: bathroom(size+1,j)=1: NEXT j  ' 番兵

   check=0
   FOR k=1 TO n
      FOR i=1 TO size
         FOR j=1 TO size
            IF bathroom(i,j)=0 THEN GOTO TRY
         NEXT j
      NEXT i
      EXIT FUNCTION
      TRY:
      IF NOT possibleToPut(bathroom(),i,j,junjo(k)) THEN EXIT FUNCTION
   NEXT k
   check=1
END FUNCTION


FUNCTION possibleToPut(bathroom(),i,j,k)
'大きさk×kのタイルの左上頂点をbathroom[i][j]にして置けるか否か判定する

   
   possibleToPut=0
   FOR ii=i TO i+k-1
      IF bathroom(ii,j)=1 THEN EXIT FUNCTION
   NEXT ii
   FOR jj=j TO j+k-1
      IF bathroom(i,jj)=1 THEN EXIT FUNCTION
   NEXT jj

   FOR ii=i TO i+k-1: FOR jj=j TO j+k-1
      bathroom(ii,jj)=1
   NEXT jj: NEXT ii
   possibleToPut=-1
END FUNCTION


Turbo Pascalプログラム

function check(junjo : junjo_type) : integer;
{ junjoで与えられるタイル順列のもとで、風呂場の左上を優先して }
{ タイルを張っていけるか否かを判定する。値はokなら1、okでないなら0 }
   label TRY;
   const size=6;  { 風呂場のサイズ }
   var    i,j,k : integer;
       bathroom : array[1..size+1, 1..size+1] of 0..1;
                  { 風呂場にタイルが張られている状況を表わす }

   function possible_to_put(i,j,k : integer) : boolean;
   { 大きさk×kのタイルの左上頂点をbathroom[i][j]にして置けるか否か判定 }
      var ii,jj : integer;
   begin
      possible_to_put:=false;
      for ii:=i to i+k-1 do if bathroom[ii][j]=1 then exit;
      for jj:=j to j+k-1 do if bathroom[i][jj]=1 then exit;

      possible_to_put:=true;
      for ii:=i to i+k-1 do
         for jj:=j to j+k-1 do bathroom[ii,jj]:=1;
   end; { of possible_to_put }

begin { of check }
   for i:=1 to size+1 do
      for j:=1 to size+1 do
         if (i < =size) and (j < =size) then bathroom[i,j]:=0  { 風呂場内 }
                                    else bathroom[i,j]:=1; { 番兵 }
   check:=0;
   for k:=1 to n do begin
      for i:=1 to size do 
         for j:=1 to size do if bathroom[i,j]=0 then goto TRY;
      exit;
      TRY: if NOT possible_to_put(i,j,junjo[k]) then exit;
   end;
   check:=1;
end; { of check }