私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「照明を消さずに端から端まで移動」(CodeIQ)を参照してください。
夜、家に帰ったら真っ暗。暗い部屋を移動するのは大変です。
そこで、室内の照明をオン・オフするスイッチが複数個所に設置されていることは珍しくありません。
例えば、廊下の電球などの場合、玄関とリビングなどのどちらでも切り替えが可能になっています。
そこで、少なくとも一つの照明をつけたまま移動することを考えます。
今回は n 個の照明が一列に並んでいるものとします。
それぞれの照明は、向かい合うスイッチと両隣のスイッチで操作できます。
最初に左端の照明だけを点灯した状態でスタートし、右端の照明だけを点灯した状態にするまで、それぞれのスイッチを操作します。
ただ、一つのスイッチを操作すると、その正面と両隣にある照明もオン・オフが切り替わります。
(左端と右端のスイッチは正面の照明と、その隣りにある照明だけが切り替わります)
例えば、n = 4 のとき、以下のような4つのスイッチと4つの照明があるとします。
左端のスイッチを押すと、左端の2つの照明が切り替わり、2つ目の照明だけが点灯します。
次に右から2つ目のスイッチを押すと、図のように3箇所が切り替わるため、右の2つが点灯します。
この場合は、まだ右端だけが点灯した状態にはなっていません。
n = 4 のとき、最初に左から2番目のスイッチを押し、次に右から2番目のスイッチを押すと、いずれかの照明が点灯したまま、右端の照明だけを点灯するようにできます。
つまり、最短で2回スイッチを押すと、目的を満たすことができます。
標準入力から n が与えられたとき、最短の手順を求め、スイッチを押した回数を標準出力に出力してください。
なお、n は 2≦n≦16を満たす整数とします。
【入出力サンプル】
標準入力
4
標準出力
2
Rubyで解答しています。
$Memo = {} def switchLight(now, mask, sw) lt = 0 if sw == 0 then lt = 0b11 else lt = mask & ((0b11 << sw) | (1 << (sw-1))) end #printf("now=%05b, mask=%05b, sw=%d, lt=%05b, ret=%05b\n", now, mask, sw, lt, now ^ lt) return mask & (now ^ lt) end def solve(n) st = 1 ed = 1 << (n-1) mask = 0 for i in 0...n mask |= 1 << i end $Memo[st] = true queue = [st] cnt = 1 while true nq = [] for q in queue for i in 0...n r = switchLight(q, mask, i) if $Memo[r] then next elsif r == ed then return cnt else nq << r $Memo[r] = true end end end cnt += 1 queue = nq end end # main while line = gets line.strip! next if line.empty? p solve(line.to_i) end
この出題者の問題の中では簡単な方と思います。
入力値を数値にしてsolve()に渡し、結果を印字します。
stは初期状態で最下位ビットだけが1になっています。
edは終了状態で入力値-1ビット目だけが1になっています。
$Memoは同じ状態を省くために使用し、キーが明かりの状態で値はtrueになります。初期状態は最初から存在するので$Memoに追加しておきます。
queueは途中状態の明かりの状態を全て保持しておくための配列です。
queueから状態を1つ取り出し、switchLight()で任意のスイッチを押します。29行目のforループのカウンタiがスイッチの場所を示します。
スイッチを押したら明かりの状態が返ってくるので、$Memoにその状態があるかをチェックします。あった場合、状態の繰り返しになるので無視します。
なかった場合、終了状態になっているかをチェックし、終了状態ならぉのときのcntを返却して終了します。cntは何回目かを表すカウンタです。
終了状態になっていなければnqに状態を追加します。
queueが空になったらcntをインクリメントし、nqをqueueと置き換えて処理を繰り返します。
スイッチを押した時の明かりの状態を計算します。
ちょっと珍しいタイプ(全パターンやらなくても良い)の問題だったような気がします。
私は最初に(仕事で使えるレベルで)習得した言語がC言語なこともあってビット演算は結構好きです。RubyとかPythonの場合でもビット表現できると配列に比べて簡単に計算できますし、配列のコピーをしなくても良いので楽です。