問題
ボウリングのピンが一列に並んだ「列」が $N$ 本あり、$i$ 本目の列には $a_i$ 本のピンが隙間なく並んでいる。2 人が交互に次のどちらかを行う。
- どれか 1 列から、ピンを 1 本倒す
- どれか 1 列から、隣り合う 2 本のピンを倒す
倒したピンは取り除かれ、列の途中を倒すと列は 2 つに分かれる(例: 長さ 5 の列の 2 本目を倒すと、長さ 1 と長さ 3 の 2 列になる)。
最後のピンを倒した人の負け(misère ルール)。両者が最善を尽くすとき、先手が勝つなら First、後手が勝つなら Second を出力せよ。$T$ 個のテストケースに答えること。
入力形式
T
N_1
a_1 a_2 ... a_{N_1}
N_2
...
制約
| # | 条件 |
|---|---|
| 1 | $1\le T\le 10^5$ |
| 2 | $1\le N\le 40$、$1\le a_i$ |
| 3 | 各テストケースで $\sum a_i\le 40$ |
| 4 | 入力はすべて整数 |
入出力例
入力例1
4
1
1
2
1 5
1
8
2
2 2出力例1
Second
Second
First
Second(1 本だけなら先手が倒すしかなく負け。$\{1,5\}$ と $\{8\}$ と $\{2,2\}$ は、下の「よくあるミス」の 3 つの近道がそれぞれ誤る最小の例)
背景と出典
Kayles は Dudeney(1908)や Berlekamp・Conway・Guy『Winning Ways』で扱われる古典的な除去ゲーム(私の記憶。原典は本セッションでは未確認)。通常ルールでは Sprague–Grundy の定理により、各列の Grundy 数 $G(n)$ の XOR が 0 かどうかで勝敗が決まる。Kayles の $G(n)$ は $n\ge 72$ で周期 12 になることが知られている(同じく未確認)。
misère ルールでは、和のゲームの勝敗が各成分の Grundy 数だけでは決まらない。例外が Nim で、「すべての山が 1 以下なら XOR が 0 のとき先手勝ち、そうでなければ通常どおり XOR≠0 のとき先手勝ち」という規則(Bouton の misère 版)で正しく解ける。本セッションでも、総数 12 以下の Nim の全 271 局面で全探索と一致した(実測)。
この「Nim の直し方」を、Grundy 数経由で Kayles に移すと誤る。Conway はこれを「tame(手なずけられる)ゲームかどうか」の問題として整理し、Plambeck が misère 商(misère quotient)で Kayles を解いた、と私は理解している(どちらも原典は未確認)。本問は misère 商を使わず、和が 40 以下という制約で全状態を数え上げられることを使う。
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
局面は「各列の長さの多重集合」で決まる。列の順番は勝敗に関係しないので、長さを昇順に並べたタプルを状態にしてメモ化する。長さ 0 の列は捨てる。ピンが 1 本も無い局面で手番が回ってきたら、直前に相手が最後のピンを倒したので手番側の勝ち。
ヒント2(アプローチ)
状態数は「和が 40 以下の分割の個数」で、合計 215307 個(実測)。1 状態からの手は、どの長さの列を選ぶか(同じ長さは 1 回だけ試す)× 1 本か 2 本か × 左右に残す長さ(対称なので半分)。相手を負け局面に送れる手が 1 つでもあれば勝ち。
ヒント3(誘導)
def win(state): # state: 昇順タプル
if not state: return True
for 各列 h(重複は 1 回):
for take in (1, 2):
for left in range(0, (h - take)//2 + 1):
right = h - take - left
nxt = 残りの列 + [left, right の 0 でないもの] を昇順に
if not win(nxt): return True
return False
テストケースをまたいでメモを共有すれば、全ケース合計で状態数ぶんしか計算しない。
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
sys.setrecursionlimit(10000)
def main():
data = sys.stdin.buffer.read().split()
t = int(data[0])
pos = 1
memo = {}
def win(state):
# state: 長さ 1 以上の列の長さを昇順に並べたタプル。手番の人が勝つか(misère)
if not state:
return True # 自分の番でピンが無い = 相手が最後の 1 本を取った → 自分の勝ち
r = memo.get(state)
if r is not None:
return r
res = False
lst = list(state)
seen = set()
for idx, h in enumerate(lst):
if h in seen:
continue
seen.add(h)
rest = lst[:idx] + lst[idx + 1:]
for take in (1, 2):
if take > h:
break
for left in range(0, (h - take) // 2 + 1):
right = h - take - left
nxt = rest + [v for v in (left, right) if v > 0]
if not win(tuple(sorted(nxt))):
res = True
break
if res:
break
if res:
break
memo[state] = res
return res
out = []
for _ in range(t):
n = int(data[pos]); pos += 1
a = sorted(int(v) for v in data[pos:pos + n]); pos += n
out.append("First" if win(tuple(a)) else "Second")
print("\n".join(out))
main()
計算量: 状態数 $P(\le40)=215307$(和が 40 以下の分割の総数、実測)× 1 状態あたりの手 $O(S^2/2)$ が上限。勝ち手が見つかった時点で打ち切るので実際はずっと少ない。和 40 以下の全分割 215307 ケースを 1 回の入力で与えて 1.74〜2.26 s(Python 3.14.7、M1 Mac、実測。2 回測って揺れた)。再帰の深さは高々ピン総数 40。
Step-by-Step 解説
通常ルールの Sprague–Grundy の定理は「最後に動けない人が負け」という終了条件に依存する。misère では終了条件が反転し、和のゲームの勝敗が成分の Grundy 数だけでは決まらない。実際、通常ルールの Kayles の Grundy 数 XOR でそのまま判定すると、和 40 以下の 215307 ケース中 1121 ケースで誤る(実測)。
Nim の misère 規則は「全部の山が 1 以下のときだけ判定を反転」。これを「全部の列の Grundy 数が 1 以下のときだけ反転」と Kayles に移すと、215307 ケース中 1661 ケースで誤る(実測)。最小の反例は 2 つ。
- $\{1,5\}$: $G(1)=1,G(5)=4$ で XOR=5≠0 → 規則は「先手勝ち」、正解は
Second - $\{8\}$: $G(8)=1$ で全部 1 以下、XOR=1 → 規則は「後手勝ち」、正解は
First
Grundy 数が同じでも、misère での「中身」は違う(長さ 1 の列と長さ 8 の列はどちらも $G=1$ だが、misère では別物)。
通常ルールでは $G\oplus G=0$ なので同じ長さの 2 列は消してよい。misère ではこれが成り立たない。$\{2,2\}$ を消すと「空 = 手番側の勝ち」になるが、正解は Second(先手の手は $\{1,2\}$ か $\{2\}$ にする 2 通りで、どちらからも後手は「1 本だけの列を 1 つ残す」手を打てる)。この近道は 215307 ケース中 11004 ケースで誤る(実測)。
列の順序を捨てて昇順タプルにする、長さ 0 を捨てる、同じ長さの列は 1 回だけ試す、左右対称な切り方は半分だけ試す。どれも勝敗を変えない変換で、状態数と手の数を減らす。正規化をしない「ピン列そのもの(0/1 の文字列)」の全探索と、列の間の空きを含めて 16 文字以下の 296 局面で一致した(実測)。
$T\le10^5$ でも、全テストケースで訪れる状態の合計は 215307 を超えない。メモを main の中で 1 つだけ持つのはこのため。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 通常ルールの Grundy XOR | $O(S^2)$ 前計算 | 不採用 | misère では誤る(1121/215307) |
| Nim の misère 規則を Grundy 経由で移す | 同上 | 不採用 | Kayles は Nim と同じ形ではない(1661/215307 誤り) |
| 状態の多重集合を全探索+メモ(本解) | 状態数 215307 | 採用 | 和 $\le40$ なら全状態を数え上げられる。ピン列そのものの全探索と 296 局面一致 |
| misère 商(Plambeck) | 商の表を引くだけ | 不採用 | 商の構成は本セッションで再現・検証していない。和の制約が無いならこれが本命 |
| ピン列を 0/1 文字列のまま全探索 | 状態数が指数的に増える | 検算用のみ | 正規化が正しいことの照合に使った |
前提が変わったら:
- 和の上限が 40 から 1000 になると分割数が爆発する(和がちょうど 100 の分割だけで $p(100)=190569292$ 個)。misère 商を構成して表を引く方法に切り替える
- 通常ルール(最後に取った人が勝ち)なら Grundy XOR で $O(S^2)$、和の上限も実質不要
- Nim(山から好きな数だけ取る)の misère なら Bouton の規則で $O(N)$。本セッションで 271 局面一致を確認済み
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 通常ルールの Grundy XOR で判定 | misère でも SG 定理が効くと思う | 状態の全探索(1121/215307 誤答) |
| 「Grundy 数が全部 1 以下なら反転」 | Nim の規則がどのゲームにも効くと思う | 同上(1661/215307 誤答、最小反例 $\{1,5\}$・$\{8\}$) |
| 同じ長さの 2 列を消す | 通常ルールの $G\oplus G=0$ を持ち込む | 消さない(11004/215307 誤答、最小反例 $\{2,2\}$) |
| 空の局面を「手番側の負け」にする | 通常ルールの終了条件のまま | 空で手番 = 相手が最後を取った = 手番側の勝ち |
| メモをテストケースごとに作り直す | 関数の中で memo = {} | main で 1 つだけ持つ |
次のステップ
- 発展1: misère 商の考え方で $\{1,5\}$ と $\{1\}$ が「違うクラス」になる理由を、本問の全探索の表(どの状態と足すと勝敗が変わるか)で確かめる
- 発展2: Dawson's Kayles(1 本倒すと両隣も倒れる)の misère 版を同じ方法で解き、通常ルールとの差を数える
- 発展3: Plambeck の misère 商の原典を読み、本問の全探索の結果と照合する(本セッションでは未確認)
検証ログ(本セッションで実行した数値)
- 正規化の照合(実測): ピン列を 0/1 文字列のまま正規化せず全探索した結果と、列の間の空きを含めて 16 文字以下の全 296 局面で一致
- Nim の misère 規則(実測): 総数 12 以下の Nim の全 271 局面で、Bouton の misère 規則と全探索が一致(規則そのものは正しいことの確認)
- 誤った方法(実測): 和 40 以下の全 215307 ケースで、通常ルールの XOR は 1121、Nim 規則の移植は 1661、等しい 2 列の消去は 11004 ケース誤答。和 20 以下(2713 ケース)ではそれぞれ 135・121・317
- 入力例(実測):
SecondSecondFirstSecond - 実行時間(実測): 和 40 以下の全分割 215307 ケースを 1 入力で 1.74 s と 2.26 s(2 回。機械の負荷で揺れる)。和 30 以下(28628 ケース)は 0.21 s
- Grundy 数(実測): $G(0..19)=0,1,2,3,1,4,3,2,1,4,2,6,4,1,2,7,1,4,3,2$(通常ルール、誤答の比較に使用)
- 扱っていない範囲: misère 商の構成、和 41 以上、Kayles の Grundy 数の周期 12 の確認、Dudeney・Conway・Plambeck の原典