私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「〇×ゲームの手順は何通り?」(CodeIQ)を参照してください。
3×3のマス目を使って〇×ゲーム(三目並べ)を行います。
2人が交互に〇と×を空いているマスに記入していき、同じ記号が縦、横、斜めのいずれかに3つ並ぶとその記号の人が勝ちです。
すべてのマスが埋まっても3つ並ばないとき、その勝負は引き分けとなります。
例えば、以下の左図のような順に書いていったとき、×が横に3つ並んだので×の勝ちとなります。
一方、右図のような順に書いていったとき、いずれも3つ並ばないため、引き分けとなります。
(必ず〇の人から始め、勝負が決まった時点で終了するものとします)
標準入力から整数 n が与えられたとき、ちょうど n 手でどちらか一方が勝つまでの手順が何通りあるかを求めてください。
なお、n は5以上9以下の整数です。
例えば、n = 5 のとき、〇の配置は、以下の8通りが考えられます。
それぞれについて、残りのマスのうち2箇所に×が入るため、手順を考えると全部で1440通りあります。
【入出力サンプル】
標準入力
5
標準出力
1440
Rubyで解答しています。
#!/usr/bin/ruby $Memo = {} def check(brd, pl) return true if (brd[0]==pl) && (brd[1]==pl) && (brd[2]==pl) return true if (brd[3]==pl) && (brd[4]==pl) && (brd[5]==pl) return true if (brd[6]==pl) && (brd[7]==pl) && (brd[8]==pl) return true if (brd[0]==pl) && (brd[3]==pl) && (brd[6]==pl) return true if (brd[1]==pl) && (brd[4]==pl) && (brd[7]==pl) return true if (brd[2]==pl) && (brd[5]==pl) && (brd[8]==pl) return true if (brd[0]==pl) && (brd[4]==pl) && (brd[8]==pl) return true if (brd[2]==pl) && (brd[4]==pl) && (brd[6]==pl) return false end def solve(n, i, brd) return $Memo[brd] if $Memo[brd] != nil chk = check(brd, i%2) if (i==n) && chk then return 1 elsif (i==n) && !chk then return 0 elsif (i!=n) && chk then return 0 end ret = 0 i += 1 for k in 0...9 next if brd[k] != nil nb = brd.dup nb[k] = i%2 ret += solve(n, i, nb) end $Memo[brd] = ret return ret end # main while line = gets line.strip! next if line.empty? p solve(line.to_i, 0, Array.new(9)) end
入力値を数値にしてsolve()の第一引数、第二引数は手番で初期値0、第三引数は盤面で全要素がnilの要素数9の配列を渡し、結果を印字します。
三目並べのパターンを列挙します。
18行目はメモに計算結果があるかをチェックし、あればその値を返します。
19行目でどちらかが勝っているかをチェックします。奇数手番の場合は1、偶数手番の場合は0がプレーヤーなので、手番iを2で割った余りを勝ち負け判定対象のプレイヤーとします。
21行目は手番が入力値の時にその手番のプレイヤーが勝っている場合で1を返します。
22行目は手番が入力値の時に勝ちが確定していない場合です。この場合は問題のパターンにならないので0を返します。
23行目は手番が入力値でない時にどちらかが勝っている場合で、これも問題のパターンにならないので0を返します。
27行目で手番を1つ進めます。
29行目のループで盤面の空いている場所(nilの要素)に手番の値(手番iを2で割った余り)を書き込み、solve()を再帰呼び出しします。
返却値を全て加算することでパターン数を集計します。
37行目で特定の盤面から条件を満たす結果のパターン数をメモに記録します。
retを返却して終了します。
手番プレーヤーが勝っていればtrue、そうでなければfalseを返します。
単純に、手番プレイヤーの値(引数pl)が盤面(引数brd)の縦横斜めに並んでいるかを全てチェックします。
最初に書いた様にOEISでtic-tac-toeを調べると答えが見つかるのですが、式がなかったので自分で実装しました。盤面を配列にしましたが、2bitずつ使って0b01と0b10を手番プレイヤーの値、0b00を空きとして扱えばもう少し簡潔かつ高速に実装できました。