Pythonでストーンゲームの勝敗を判定!Amalが勝てるかチェックするプログラム
2人のプレイヤーAmalとBimalがゲームを行い、Amalが先手です。最初、山にはn個の石が積まれています。各プレイヤーは自分のターンに、山から「平方数(0以外)」個の石を取り除かなければなりません。そして、これ以上手を打てなくなったプレイヤーが負けとなります。nが与えられたとき、Amalがこのゲームに勝てるかどうかを判定しましょう。
例えば、n = 21の場合、答えは True になります。Amalが最初に16個の石を取ると、Bimalは4個取り、最後にAmalが残りの1個を取って勝利できるからです。
解法のアプローチ
この問題は動的計画法(DP)で効率よく解けます。dp[i] を「i個の石が残っているとき、その手番のプレイヤーが勝てるか」を表す真偽値とします。もし「iからある平方数を引いた局面(i − 平方数)で相手が負ける」のであれば、現在の手番のプレイヤーは必ず勝てます。
アルゴリズムの手順
- n以下のすべての平方数をリスト squares に生成します(差分が奇数ずつ増える性質を利用)
- サイズ(n+1)のDPテーブル dp を用意し、dp[0] = False(石が0個なら手番のプレイヤーは負け)と初期化します
- k = 1 から n まで順に処理し、k から平方数を引いた局面 dp[k − 平方数] が False(相手が負ける)になるものが1つでもあれば、dp[k] = True とします
- 最後に dp[n] を返せば、Amal(先手)が勝てるかどうかが分かります
実装例
以下のPythonコードで理解を深めましょう。
def solve(n):
squares = []
square = 1
increase = 3
while square <= n:
squares.append(square)
square += increase
increase += 2
squares.append(square)
dp = [None] * (n + 1)
dp[0] = False
for k in range(1, n + 1):
s = 0
dp[k] = False
while squares[s] <= k and not dp[k]:
if not dp[k - squares[s]]:
dp[k] = True
s += 1
return dp[-1]
n = 21
print(solve(n))
入力
21
出力
True
このアルゴリズムの時間計算量は O(n√n) 程度であり、nが大きくなっても十分高速に動作します。ゲーム理論の基本的な勝敗判定パターンの一つなので、競技プログラミングでも応用範囲が広い考え方です。
-
Pythonで左右の部分木の入れ替えにより2つの二分木を一致させられるか判定する方法
問題の概要 2つの二分木が与えられたとき、任意のノードについて左部分木と右部分木を何度でも入れ替えてよいと仮定します。この操作を繰り返すことで、1つ目の木を2つ目の木とまったく同じ形に変換できるかどうかを判定するのが、この記事で扱う問題です。 例えば、次のような2つの木が入力として与えられた場合、左右の入れ替えによって一致させられるため、出力は True になります。 解決のアプローチ この問題は、幅優先探索(BFS)の考え方を使い、木をレベル(深さ)ごとに処理しながらノードの値を比較することで解けます。左右の入れ替えによって同じレベル内の値の並び順は反転し得るため、「順方向」または「逆方
-
Pythonで与えられたグラフが2部グラフかどうかを判定するプログラム
2部グラフとは無向グラフが与えられたとき、そのグラフが2部グラフ(バイパータイトグラフ)であるかどうかを判定する方法を解説します。2部グラフとは、グラフのすべての頂点を2つの集合 A と B に分割でき、グラフ内のすべての辺 {u, v} が必ず一方の端点 u が集合 A、もう一方の端点 v が集合 B に属するようなグラフのことです。つまり、同じ集合内の頂点同士を結ぶ辺(A-A や B-B)が一切存在しないグラフです。例として、次のようなグラフを考えてみましょう。この場合、頂点 [0, 4] を集合 A に、[1, 2, 3] を集合 B に分類できます。すべての辺は A から B、または