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


問題2

10点

 図1のような 6×6 の正方形の風呂場にタイルを張りたい。使うことのできる タイルとして、図2に示すように 1×1 のものが18個、2×2 のものが9個、 3×3 のものが4個、4×4 のものが1個ある。これら4種類のタイルをそれぞれ 何個かずつ用い、風呂場に隙間なくタイルを張りたい。使わないタイルがあって もよいが、どのタイルも風呂場からはみ出たり、重なったりしてはならない。 タイルの張り方が全部で何通りあるかを求めたい。ただし、対称なものや、 回転すると重なるものも異なる張り方として数える。
 下記に示す C, Quick BASIC, Pascal のプログラムリストは、いずれも この張り方の数を求めるプログラムであるが、手続き(関数) check が未完成 であるので、完成せよ。どのようなアルゴリズムを用いているかは、 プログラム内にコメントとして書かれている。

          -------------------------------
         |   |   |   |   |   |   |   |   |
         |---+---+---+---+---+---+---+---|
         |   |   |   |   |   |   |   |   |
         |---+---+---+---+---+---+---+---|      N
         |   |   |   |   |   |   |   |   |      ↑
         |---+---+---+---+---+---+---+---|      |
         |   |   |   |   |   |   |   |   |      +
         |---+---+---+---+---+---+---+---|      |
         |   |   |   |   |   |   |   |   |
         |---+---+---+---+---+---+---+---|
         |   |   |   |   |   |   |   |   |
         |---+---+---+---+---+---+---+---|
         |   |   |   |   |   |   |   |   |
         |---+---+---+---+---+---+---+---|
         |   |   |   |   |   |   |   |   |
          -------------------------------

図1(風呂場の間取り)8×8

        ---                   -------
       |   |  1×1:18個     |   |   |
        ---                  |---+---|   2×2:9個
                             |   |   |
       ---------------        -------
      |   |   |   |   |      
      |---+---+---+---|      -----------
      |   |   |   |   |     |   |   |   |
      |---+---+---+---|     |---+---+---|
      |   |   |   |   |     |   |   |   |
      |---+---+---+---|     |---+---+---|
      |   |   |   |   |     |   |   |   |
       ---------------       -----------
         4×4:1個       3×3:4個

図2(使用可能なタイル)


Cプログラム

#include < stdio.h >

#define MENSEKI  36   /* 風呂場の広さ(面積) */
#define SYURUI    4   /* タイルの種類 */
#define MAXCOMBI 50   /* タイルの選び方は高々これだけしかない */

int  tile_max[SYURUI]={18,9,4,1},  /* 利用可能なタイルの種類毎の個数 */
    tile_size[SYURUI]={1,4,9,16};  /* タイルの種類毎の大きさ(面積) */

int possible_combinations(int[][SYURUI]);
int place(int []);
int check(int [], int);
int there_is_another(int [], int);


void main()
{
   int i,j,combi,
       count, /* タイルの張り方の総数(求めるもの) */
       possible_tile_list[MAXCOMBI][SYURUI],   
         /* 面積の和が風呂場の広さに等しいようなタイルの選び方のリスト */
         /* possible_tile_list[i][j]はi番目の選び方における第j種のタイルの数 */
         /* ただし、i=0番目が最初の選び方; j=0,1,2,3 */
       picked_tiles[SYURUI]; /* 選ばれた1組のタイル */

   combi=possible_combinations(possible_tile_list);
      /* 面積条件を満たすタイルの組合せ全てをpossible_tile_listに求める */
      /* combiは得られた組合せの数 */

   for (count=0, i=0; i < combi; i++) { 
   /* タイル各組に対して、張り方の数を加算していく */
      for (j=0; j < SYURUI; j++) 
         picked_tiles[j]=possible_tile_list[i][j];
      count+=place(picked_tiles); 
   }
   printf("タイルの張り方は%d通りあります。\n", count);
   exit(0);
}

/* 面積の和が風呂場の広さに等しいようなタイルの選び方をすべて求める */
/* 関数値は得られた組合せの数 */
int possible_combinations(int possible_tile_list[][SYURUI])
{
   int t0,t1,t2,t3,count=0;

   for (t0=0; t0 < =tile_max[0]; t0++)
      for (t1=0; t1 < =tile_max[1]; t1++)
         for (t2=0; t2 < =tile_max[2]; t2++)
            for (t3=0; t3 < =tile_max[3]; t3++)
               if (t0*tile_size[0]+t1*tile_size[1]+
                   t2*tile_size[2]+t3*tile_size[3]==MENSEKI) {
                  possible_tile_list[count][0]=t0;
                  possible_tile_list[count][1]=t1;
                  possible_tile_list[count][2]=t2;
                  possible_tile_list[count][3]=t3;
                  count++;
               }
   return count;
}

/* 選んだ1組のタイルに対して、何通りの張り方があるかを計算する */
int place(int picked_tiles[])
{
   int i,j,N,
       n,               /* 使われるタイルの総数 */
       count,           /* 求めたい張り方の数 */
       junjo[MENSEKI];  /* タイルの張り順 */
          /* junjo[i]=n はi番目に第n種のタイルを張ることを表わす */

   for (i=0, n=0; i < SYURUI; i++)
      for (j=0; j < picked_tiles[i]; j++) junjo[n++]=i;
   N=n-1;  /* junjo[0]〜junjo[N]が使われる */

   count=0;
   do { count+=check(junjo,N); } while (there_is_another(junjo,N));
      /* 全ての張り順について、その張り方が可能か否かでcountを増やしていく */
   return count;
}

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

   宣言部も含めて、この関数checkを完成せよ。

}

/* junjo[0]〜junjo[N]で与えられる順列に対して、*/
/* 辞書式順序で次の順列があれば1を、なければ0を返す */
/* 副作用として、junjoの内容を次の順列に書き換える */
int there_is_another(int junjo[], int N)
{
   int i,j,temp;

   for (i=N-1; i > =0; i--) if (junjo[i] < junjo[i+1]) break;
   if (i < 0) return 0;

   for (j=N; j > 0; j--) if (junjo[i] < junjo[j]) break;
   temp=junjo[i]; junjo[i]=junjo[j]; junjo[j]=temp;
   for (i=i+1, j=N; i < j; i++, j--) {
      temp=junjo[i]; junjo[i]=junjo[j]; junjo[j]=temp;
   }
   return 1;
}


Quick BASICプログラム

DEFINT A-z
CONST menseki=36   ' 風呂場の広さ(面積)
CONST syurui=4     ' タイルの種類
CONST maxcombi=50  ' タイルの選び方は高々これだけしかない
CONST size=6       ' 風呂場のサイズ
DECLARE FUNCTION possibleCombinations()
DECLARE FUNCTION place(pickedTiles())
DECLARE FUNCTION check(junjo(),n)
DECLARE FUNCTION thereIsAnother(junjo(),n)
DIM possibleTileList(maxcombi,syurui)
    '面積の和が風呂場の広さに等しいようなタイルの選び方のリスト
    'possibleTileList(i,j)はi番目の選び方における第j種のタイルの数
DIM tileSize(syurui)        ' 各タイルのサイズ
DIM tileMax(syurui)         ' 各種類のタイルの個数
DIM pickedTiles(syurui)     ' 選ばれた1組のタイル


   '利用可能なタイルの種類毎の個数
    tileMax(1)=18: tileMax(2)=9: tileMax(3)=4: tileMax(4)=1
   'タイルの種類毎の大きさ(面積)
    tileSize(1)=1: tileSize(2)=4: tileSize(3)=9: tileSize(4)=16

   '条件を満たすタイルの組合せ全てをpossibleTileListに求める
   combi=possibleCombinations   ' combiは得られた組合せの数
  
   count=0  ' タイルの張り方の総数(求めるもの)
   'タイル各組に対して張り方の数を加算していく
   FOR i=1 TO combi
      FOR j=1 TO syurui: pickedTiles(j)=possibleTileList(i,j): NEXT j
      count=count+place(pickedTiles())
   NEXT i
   PRINT "タイルの張り方は"; count; "通りあります。"
END

FUNCTION possibleCombinations
'面積の和が風呂場の広さに等しいようなタイルの選び方をすべて求める
'関数値は得られた組合せの数                                        

   SHARED possibleTileList(),tileMax(),tileSize()

   count=0
   FOR t1=0 TO tileMax(1): FOR t2=0 TO tileMax(2)
      FOR t3=0 TO tileMax(3): FOR t4=0 TO tileMax(4)
         s12=t1*tileSize(1)+t2*tileSize(2)
         s34=t3*tileSize(3)+t4*tileSize(4)
         IF s12+s34=menseki THEN
            count=count+1
            possibleTileList(count,1)=t1
            possibleTileList(count,2)=t2
            possibleTileList(count,3)=t3
            possibleTileList(count,4)=t4
         END IF
      NEXT t4: NEXT t3
   NEXT t2: NEXT t1
   possibleCombinations=count
END FUNCTION

FUNCTION place(pickedTiles())
'選んだ1組のタイルに対して、何通りの張り方があるかを計算する

   DIM junjo(menseki)  ' タイルの張り順
                       ' junjo[i]=n はi番目に第n種のタイルを張ることを表わす

   n=0  ' nは使われるタイルの総数
   FOR i=1 TO syurui: FOR j=1 TO pickedTiles(i)
      n=n+1: junjo(n)=i
   NEXT j: NEXT i
   'junjo[1]〜junjo[n]が使われる

   count=0  ' 求めたい張り方の数
   '全ての張り順について、その張り方が可能か否かでcountを増やしていく
   DO
      count=count+check(junjo(),n)
   LOOP WHILE thereIsAnother(junjo(),n)
   place=count
END FUNCTION

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

  宣言等も含めて、この関数プロシージャ check を完成せよ。

END FUNCTION

FUNCTION thereIsAnother(junjo(),n)
'junjo[1]〜junjo[n]で与えられる順列に対して、
'辞書式順序で次の順列があれば-1を返すと同時にjunjoの内容を次の順列に書き換え、
'なければ0を返す

   FOR i=n-1 TO 1 STEP -1
      IF junjo(i) < junjo(i+1) THEN EXIT FOR
   NEXT i
   IF i < =0 THEN thereIsAnother=0: EXIT FUNCTION

   j=n: DO WHILE junjo(i) > =junjo(j): j=j-1: LOOP
   SWAP junjo(i),junjo(j)
   i=i+1: j=n
   DO WHILE i < j
      SWAP junjo(i),junjo(j): i=i+1: j=j-1
   LOOP
   thereIsAnother=-1
END FUNCTION


Turbo Pascal プログラム

program tiling;
   const menseki=36;   { 風呂場の広さ(面積) }
         syurui=4;     { タイルの種類 }
         maxcombi=50;  { タイルの選び方は高々これだけしかない }
   type  syurui_type = array[1..syurui]  of integer;
   var i,j,combi : integer;
           count : integer; { タイルの張り方の総数(求めるもの) }
       possible_tile_list : array[1..maxcombi, 1..syurui] of integer;
         { 面積の和が風呂場の広さに等しいようなタイルの選び方のリスト }
         { possible_tile_list[i][j]はi番目の選び方における第j種のタイルの数 }
       tile_size    : syurui_type; { 各タイルのサイズ(面積) }
       tile_max     : syurui_type; { 各種類のタイルの個数 }
       picked_tiles : syurui_type; { 選ばれた1組のタイル }

   function possible_combinations : integer;
   { 面積の和が風呂場の広さに等しいようなタイルの選び方をすべて求める }
   { 関数値は得られた組合せの数 }
      var t1,t2,t3,t4,count : integer;
   begin
      count:=0;
      for t1:=0 to tile_max[1] do 
         for t2:=0 to tile_max[2] do
            for t3:=0 to tile_max[3] do
               for t4:=0 to tile_max[4] do
                  if t1*tile_size[1]+t2*tile_size[2]+
                     t3*tile_size[3]+t4*tile_size[4]=menseki then
                  begin
                     count:=count+1;
                     possible_tile_list[count,1]:=t1;
                     possible_tile_list[count,2]:=t2;
                     possible_tile_list[count,3]:=t3;
                     possible_tile_list[count,4]:=t4;
                  end;
      possible_combinations:=count;
   end; { of possible_combinations }


   function place(var picked_tiles : syurui_type) : integer;
   { 選んだ1組のタイルに対して、何通りの張り方があるかを計算する }
      type junjo_type=array[1..menseki] of integer;
      var   i,j : integer;
              n : integer;     { 使われるタイルの総数 }
          count : integer;     { 求めたい張り方の数 }
          junjo : junjo_type;  { タイルの張り順 }
                { junjo[i]=n はi番目に第n種のタイルを張ることを表わす }

      function check(junjo : junjo_type) : integer;
      { junjoで与えられるタイル順列のもとで、風呂場の左上を優先して }
      { タイルを張っていけるか否かを判定する。値はokなら1、okでないなら0 }

        宣言部も含めて、この関数checkを完成せよ。

      end; { of check }


      function all_over : boolean;
      { junjo[1]〜junjo[n]で与えられる順列に対して、}
      { 辞書式順序で次の順列があればfalseを返すと同時にjunjoの内容を次の順列 }
      { に書き換え、なければ(全ての順列のチェックが終了ならば)trueを返す }
         var i,j,temp : integer;
      begin
         i:=n; repeat i:=i-1 until (i=0) or (junjo[i] < junjo[i+1]);
         if i < =0 then begin all_over:=true; exit; end;

         j:=n; while junjo[i] >= junjo[j] do j:=j-1;
         temp:=junjo[i]; junjo[i]:=junjo[j]; junjo[j]:=temp;
         i:=i+1; j:=n;
         while i < j do begin
            temp:=junjo[i]; junjo[i]:=junjo[j]; junjo[j]:=temp;
            i:=i+1; j:=j-1;
         end;
         all_over:=false;
      end; { of all_over }

   begin { of place }
      n:=0;
      for i:=1 to syurui do
         for j:=1 to picked_tiles[i] do begin n:=n+1; junjo[n]:=i; end;
         { junjo[1]〜junjo[n]が使われる }

      count:=0;
      { 全ての張り順について、その張り方が可能か否かでcountを増やしていく }
      repeat count:=count+check(junjo) until all_over;
      place:=count;
   end; { of place }

begin
   { 利用可能なタイルの種類毎の個数 }
     tile_max[1]:=18; tile_max[2]:=9; tile_max[3]:=4; tile_max[4]:=1;
   { タイルの種類毎の大きさ(面積) }
     tile_size[1]:=1; tile_size[2]:=4; tile_size[3]:=9; tile_size[4]:=16;

   { 面積条件を満たすタイルの組合せ全てをpossible_tile_listに求める }
   combi:=possible_combinations;  { combiは得られた組合せの数 }

   count:=0;
   for i:=1 to combi do begin { タイル各組に対して張り方の数を加算していく }
      for j:=1 to syurui do picked_tiles[j]:=possible_tile_list[i,j];
      count:=count+place(picked_tiles); 
   end;
   writeln('タイルの張り方は', count, '通りあります。');
end.