問題4 解説

 実際にコップを移動してみるとすぐわかるように,コップを移動させていく方法は単純な一本道である.それゆえ,最も単純な解法は,可能なステップを逐一トレースする(実際にコップを移動させる過程を追ってみること)である. ただし,その場合でも,与えられた初期状態からトレースを開始すると,最初に実行可能なステップが 2 通りあることがあるので,その両方をトレースして,目的状態に到達するステップ数が少ない方を選ぶことになる.あるいは同じことであるが,すべてのコップが A に重ねられている状態から与えられた初期状態に到達するまでのステップ数と,すべてのコップが C に重ねられている状態から初期状態に到達するまでのステップ数を求めて,それらの小さい方を解としてもよい.
 しかし,次のことに気付けば,これらのうちの一方だけをやってみるだけで解は求められる.すべてのコップ(n 枚とする)が A に重ねられている状態から始めて,すべてのコップが C に重ねられるまでに必要な最小ステップ数を f (n) とすると,
  1. 1 番小さいコップを無視して(無いと思って),A にある n - 1 個のコップすべてを C に移すことを先ず行う(1 番小さいコップの上にはどのコップも重ねることができるので,無いと考えてもよい).これに必要なステップ数は f (n - 1) である.
  2. 次に,A に残した一番小さいコップを B に移動する.
  3. B にある一番小さいコップが無いものと思って,C にある n - 1 個のコップを A に移す.これにかかるステップ数は f (n - 1) である.
  4. B にある一番小さいコップを C に移動する.
  5. C にある一番小さいコップを無いものと思って,A にある n - 1 枚のコップを C に移動する.これに必要なステップ数は f (n - 1) である.
以上のステップ数を合計すると,
  f (n) = 3 f (n) + 2
という漸化式が得られる.特に,f (1) = 2 であるから,これを解くと,f (n) = 3n - 1 であることがわかる. この事実を使うと,C にすべてのコップが重ねられている状態から与えられた初期状態までのステップ数は,(3n - 1) - (C にすべてのコップが重ねられている状態から与えられた初期状態までのステップ数) として求められる.

 このように方針が立つと,あとはいかにコップの移動をプログラム上で実装するか(つまり,コップがそれぞれのトレイにある状態をどのように表現するとトレースのシミュレーションが容易になるか)という点だけである. コップはそれまでに積み重ねられている上に重ねて置くしかないことと,重ねられた山の一番上のものを取ることしか許されていないが,このようなデータの挿入削除の制限を持った構造は スタック (stack) という名前で良く知られているものである.スタックをプログラムで実装する方法を使えば容易にプログラムは書けるが,たとえスタックというものを知らなくても,似たようなことを行うのはむずかしいことではない.

 さて実は,このように実際にコップの移動をトレースしてみなくても,ステップ数は式の計算によって求めることもできる.
 トレイ A, B, C に重ねられているコップの大きさ(整数値)の集合をそれぞれ A, B, C で表すことにする(集合について知らない人は読み飛ばしてもよい.予選の参加者がこのような解法をすることは出題側では想定していない).また,初期状態 A, B, C から,トレイ X にすべてのコップを重ねるために必要な最小ステップ数を fX(A, B, C) で表すことにする(X は A, B, C のいずれか).A ∪ B ∪ C の中の最小値を min で表す.また,空集合を 0 で表す. まず,fC(A, B, C) は fA, fB を使って次のように表すことができる.

 この式の 1 行目は,最小のコップが C にある場合,それが無いかのごとく考えてコップをすべて C に移動すればよいことを表している.2 行目は,最小のコップが B にある場合は,その 1 個を無視して,残りをすべて A に集めてから,B に残った最小のコップを C に移動させ,その後で A にあるすべてのコップを(C にある最小のコップの存在は無視して) C に移動させればよいことを表している.3 行目は上述の 1. 〜 5. を表している.
 同様に,fB(A, B, C), fA(A, B, C) の漸化式が得られる. A, B, C をコップの初期状態とするとき,これら3つの漸化式 fA, fB, fC を連立で再帰的に計算し,fC(A, B, C) と fA(A, B, C) の小さい方を求める解とすればよい.この式の右辺の集合の要素数は左辺のそれれよりも小さくなっているので,計算はコップの総数に比例する時間 O(|A∪B∪C|) で終了することがわかる. このとき,上述の f (n) = 3n - 1 を使うと計算はより簡単になる.
 なお,繰り返しになるが,この問題は最初に述べたようにステップをトレースする方法で解くことを期待して出題しており,高校生にはむずかしい「式の再帰的計算による解法」で解くことはまったく想定していない.

 この問題は「ハノイの塔」と呼ばれる有名な問題をもとにしている.ハノイの塔の問題では,この問題のコップに相当するものの移動は A から C へも,C から A へも直接移動できることになっているが,この問題はそれらを禁止したものである.容易にわかるように,A にすべてのコップがある状態から C にすべてのコップを重ねた状態にするための最小ステップ数はハノイの塔の問題では 2n - 1 であり,この問題では上述のように 3n - 1 である.この問題の場合,3n は可能なコップの配置状態の総数に等しい.A にすべてのコップがある状態から C にすべてのコップがある状態へ移るためには,B にすべてのコップが重ねられている状態を必ず通過し,それは,移動の過程のちょうど真ん中の (3n - 1)/2 ステップ目である.

 最後に,別の解法も可能であることも指摘しておく.それは,8クイーン問題とか騎士の周遊問題といった有名問題を解くときに用いられる方法,すなわち,可能なステップの進め方を先へ先へと進めて,失敗したら最小限後戻りして別の可能なステップを先へと進める,という方法である(グラフ理論とアルゴリズムの用語を使って言うと,可能なステップの状態を頂点とし,移動可能なステップ間に辺があるようなグラフを底優先探索することに等しい.通常はそのようなグラフは意識せずに,ステップの進め方を再帰的なアルゴリズムとして記述することが多い).しかし,この問題の場合,ステップ数が最悪の場合 3n - 1 にもなるので,n がちょっと大きくなるだけで,それまでにたどって来た道筋を(失敗したときに後戻り [バックトラッキング (backtracking) という] できるように)記憶しておく必要がある [再帰的プログラムの場合には,再帰が発生するたびにそのときの関数の局所状態がメモリ内にシステムによって自動的に記憶される] )ために,メモリが実行時にオーバーフローしてしまうという決定的な欠点がある.
 さらに,別の解法として,どの状態からも「A から B」「B から A」「B から C」「C から B」の 4 通りしかコップの移動方法がないので,m 桁の 4 進数(m ステップ分のコップの移動を表す)すべてを系統的に生成して,そのうちで実際にコップの移動に対応しているものを求めるという解法もある(全数検査法).しかし,この方法では,4m 通りの場合すべてを試さなければならないので,ごく小さい m にしか対応できないという致命的欠点がある. それでも,全数検査法で解けば 4 点(手で計算することも可能),再帰で解けば 12 点取れるように入力データを決めて出題した.また,n およびそれに対応して決まる m の値の上限は,最小ステップ数が 32 ビット数で表現できる範囲内に抑えた.