JOI’95 予選 問題5 解答・解説

問題5 正解 (3点 = (a)(b)(c)各1点)

      (a) C       digit[c1]=d1; unused[d1]=false;
                  try2(p,d1);
                  unused[d1]=true;
          Pascal  digit[c1]:=d1; unused[d1]:=false;
                  try2(p,d1);
                  unused[d1]:=true;
          BASIC   digit(c1)=d1: unused(d1)=false
                  CALL\ try2((p),(d1))
                  unused(d1)=true

      (b) C       digit[c2]=d2; unused[d2]=false;
                  try3(p,d1,d2);
                  unused[d2]=true;
          Pascal  digit[c2]:=d2; unused[d2]:=false;
                  try3(p,d1,d2);
                  unused[d2]:=true;
          BASIC   digit(c2)=d2: unused(d2)=false
                  CALL try3(p,d1,d2)
                  unused(d2)=true
      (c) C       carry[0]==0   または   carry[p-1]==0
          Pascal  carry[0]=0    または   carry[p-1]=0
          BASIC}  carry(0)=0    または   carry(p-1)=0
    


 覆面算を解くアルゴリズムは、英字 'A'〜'Z' それぞれに 数字 '0'〜'9' を当てはめてみて、足し算として矛盾しないか どうかを下位の桁から順にチェックしてゆくだけの単純なものである。
 (以下の記述はPascalのプログラムに基づく。問題文のPascalおよび BASICプログラムは配列の添字が1から始まっているのに対して、Cの場合は 0から始まっていることを考慮して読むこと。)

 まず、足し算の1行目(足される数)、2行目(足す数)、3行目(和) それぞれを表わす3つの文字列を配列 word に読み込む。 word[i,j] は第i行目の先頭からj番目の文字を表わす。 3文字列のどれも同じ長さになるように先頭には空白文字が入っていることに 注意のこと。下記の計算の際は空白文字には数字0を当てはめておいて、 出力の際に先頭の0は空白に置き換えてプリントする。
 文字cに当てはめることができる最小の数字(例えば、最上位の桁 の文字は0ではない)を配列 start[c] に設定するなどの初期化を 行なった後、解を求めるための計算は try1(length) から始める。 ここで、length は文字列の長さであり、try1(p) は 足し算の第1行目の先頭からp文字目に、まだ使われていない数字を 当てはめてみる手続きである。 したがって、try1(length) は、第1行目の最も下位の桁から始める ことを意味する。 まだ使われていない数字かどうかは配列 unused[0..9] に 記録されている(unused[d]=true なら数字dはまだ使われていない)。
 try1(p) では、該当文字c1に、まだ使われていない数字のうち最も小さい もの d1 を見つけてから空欄(a)の部分を実行する。 空欄(a)では以下のことを行なう:まず、その文字に当てはめた数字を記録 し (digit[c1]:=d1)、当てはめた数字に「使用中」の印を 付けること (unused[d1]:=false) をしてから、第2行目の同じ桁 (p文字目)の文字に数字を当てはめる作業に移り (try2(p,d1))、 その作業から戻ってきたら、まだ使われていない他の数字もすべて 当てはめてみることをしたいので、ununsed[d1]:=true として 今回当てはめてみた数字を取り消し、別の数字 d1 について試してみる ことを続けられる状態に戻す。
 try2(p) は、第2行目の先頭から p 文字目について try1(p) と同様なこと を行なう。したがって、空欄(b)の答は空欄(a)と酷似している。 try2(p) では、第2行目の先頭からp文字目に適当な数字 d2 を当てはめた後、 第3行目の先頭からp文字目を決めるために try3(p) を呼び出す。
 try3(p) は、try1(p) で決めた d1 や try2(p) で決めた d2 と下からの 桁上がり carry(p) を考慮して、足し算が成り立つように第3行目の先頭 から p 文字目に適当な数字 d3 を当てはめてみる。 しかる後、この桁が最も先頭の桁である場合 (p=1) で、しかも 上の桁への桁上がりがない場合 (carry[0]=0:これが空欄(c)の答である) には、解が一つ見つかったのだからそれを出力するために 手続き found を呼び出す。