問題
ピンが一列に隙間なく並んだ「列」が $N$ 本あり、$i$ 本目の列には $a_i$ 本のピンがある。2 人が交互に、次の手を 1 回ずつ行う。
- どれか 1 列から、隣り合う 2 本のピンを倒す(1 本だけ倒すことはできない)
倒したピンは取り除かれ、列の途中を倒すと列は 2 つに分かれる(例: 長さ 6 の列の 2・3 本目を倒すと、長さ 1 と長さ 3 の 2 列になる)。
最後に手を指した人の負け(misère ルール)。言い換えると、手番が回ってきたときに指せる手が 1 つも無ければ、その人の勝ち。両者が最善を尽くすとき、先手が勝つなら First、後手が勝つなら Second を出力せよ。$T$ 個のテストケースに答えること。
入力形式
T
N_1
a_1 a_2 ... a_{N_1}
N_2
...
制約
| # | 条件 |
|---|---|
| 1 | $1\le T\le2\times10^5$ |
| 2 | $1\le N$、$1\le a_i$ |
| 3 | 各テストケースで $\sum a_i\le50$ |
| 4 | 入力はすべて整数 |
入出力例
入力例1
6
1
1
1
2
1
3
1
12
2
4 4
3
1 3 6出力例1
First
Second
Second
Second
Second
First($\{1\}$ は先手に手が無いので先手の勝ち。$\{2\}$ と $\{3\}$ は先手が唯一の手を指すと後手に手が無くなり、先手が最後に指したので負け。$\{1\}$・$\{12\}$・$\{4,4\}$ は下の「よくあるミス」の 3 つの近道がそれぞれ誤る最小の例)
背景と出典
この「隣り合う 2 本を倒す」ゲームは八進表記で 0.07、Dawson's Kayles と呼ばれる。T. R. Dawson のチェス詰め問題(Dawson's chess、八進 0.137)と同じ構造で、列の長さが 1 ずれるだけの関係にある、と私は理解している(Dawson の原典と Guy–Smith(1956)の八進ゲーム論文は本セッションでは未確認)。
通常ルール(最後に指した人が勝ち)では Sprague–Grundy の定理で各列の Grundy 数 $G(n)$ の XOR が答えを決める。本セッションで $G(n)$ を $n<194$ まで計算すると、$G(n)\ne G(n+34)$ となる $n$ は $\{0,15,17,32,35,52\}$ で、$n\ge53$ では周期 34(実測)。文献で知られる「周期 34」と合う(文献の例外位置との照合は未実施)。
misère では和のゲームの勝敗が Grundy 数だけでは決まらない(Day169-Q1 と同じ)。Conway の「tame でないゲーム」の整理、Plambeck の misère 商(misère quotient)が和の制約なしで解く道具だが、本問は和 50 以下という制約で全状態を数え上げられることを使う(misère 商の原典は未確認)。
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
局面は「各列の長さの多重集合」で決まる。さらに、長さ 1 の列ではどんな手も指せず、他の列の手にも影響しない。だから長さ 1 の列は最初から捨ててよい(misère でも正しい。指せる手の集合が変わらないので、ゲーム木がまったく同じになる)。長さ 0 も同様。
ヒント2(アプローチ)
長さ 2 以上の列だけを昇順タプルにして状態にし、メモ化全探索する。和 50 以下で長さ 2 以上の部分だけからなる分割は 204225 個(実測)。1 状態からの手は「どの長さの列か(同じ長さは 1 回)×左に残す本数(対称なので半分)」。空の状態(指せる手が無い)は手番側の勝ち。
ヒント3(誘導)
def win(state): # 長さ 2 以上の列の昇順タプル
if not state: return True # 手が無い = 手番側の勝ち(misère)
for 各列 h(同じ長さは 1 回):
for left in range(0, (h - 2)//2 + 1):
right = h - 2 - left
nxt = 残りの列 + [left, right のうち 2 以上のもの] を昇順に
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: 長さ 2 以上の列を昇順に並べたタプル。手番の人が勝つか(misère)
if not state:
return True # 動ける手が無い = 相手が最後の手を打った → 手番側の勝ち
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 left in range(0, (h - 2) // 2 + 1):
right = h - 2 - left
nxt = rest + [v for v in (left, right) if v >= 2]
if not win(tuple(sorted(nxt))):
res = True
break
if res:
break
memo[state] = res
return res
out = []
for _ in range(t):
n = int(data[pos]); pos += 1
a = sorted(v for v in map(int, data[pos:pos + n]) if v >= 2); pos += n
out.append("First" if win(tuple(a)) else "Second")
print("\n".join(out))
main()
計算量: 状態数は「和 $\le50$、部分がすべて 2 以上の分割」の 204225 個が上限。1 状態あたりの手は $O(S^2/2)$ だが、勝ち手が見つかれば打ち切る。204225 個の全状態を 1 入力で与えて 1.45 s・1.46 s(Python 3.14.7、M1 Mac、2 回実測)。
Step-by-Step 解説
長さ 1 の列に対して指せる手は無く、他の列の手の結果にも現れない。だから局面 $X\cup\{1\}$ と $X$ はゲーム木がまったく同じ(同型)で、通常ルールでも misère でも勝敗が一致する。本問では手を倒した後の残り(left, right)が 1 になったときも捨てる。Day169-Q1 の Kayles(1 本倒しもある)では長さ 1 にも手があるので捨てられなかった。状態数は和 40 以下で Kayles 側 215307 個に対し本問は 37337 個(実測)、だから和の上限を 50 に上げられた。
通常ルールでは $G\oplus G=0$ なので同じ長さの 2 列を消せる。misère ではゲーム木が変わる($\{4,4\}$ は消すと空=先手勝ちに見えるが、正解は Second)。「捨ててよい」のはゲーム木が同型になる変換だけ、が線引き。
$\{1\}$ の Grundy 数は 0 で、通常ルールなら「後手勝ち」。misère では先手に手が無い=先手の勝ち。終了条件が反転するので、和 40 以下の全 215307 局面(長さ 1 の列も含む全分割)のうち 53046 局面で誤る(実測)。Kayles の 1121 局面よりずっと多いのは、長さ 1 の列(と $G=0$ の列)がたくさんある局面で、どれも「指せない」ことが勝ちに効くため。
「全部の列の $G\le1$ なら XOR=0 で先手勝ち、そうでなければ通常どおり」を移すと 25380 局面で誤る。最小の反例は $\{12\}$($G(12)=2$ で XOR≠0 → 規則は First、正解は Second)。
正規化をしない「ピンの 0/1 文字列」をそのまま全探索した結果と、両端がピンで長さ 16 以下の全 32768 文字列で一致(実測)。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 通常ルールの Grundy XOR | $O(S^2)$ | 不採用 | 53046/215307 局面で誤り |
| Nim の misère 規則を Grundy 経由で移す | 同上 | 不採用 | 25380/215307 誤り、最小反例 $\{12\}$ |
| 等しい 2 列も消して状態を減らす | 状態数が減る | 不採用 | 26570/215307 誤り、最小反例 $\{4,4\}$ |
| 長さ 1 を捨てた多重集合の全探索(本解) | 状態 204225 | 採用 | ピン文字列の全探索と 32768 件一致 |
| misère 商(Plambeck) | 表引き | 不採用 | 本セッションで商を構成・検証していない。和の上限が無いならこれが本命 |
前提が変わったら:
- 和の上限が 55 になると、ランダムに選んだ $10^5$ 局面で 2.64 s、60 で 4.39 s(実測)。60 を超えるなら misère 商か、Grundy 数が 0・1 の列を別扱いする部分的な理論が要る
- 通常ルールなら Grundy XOR で $O(S^2)$、周期 34 を使えば列の長さが $10^{18}$ でも $O(1)$
- 1 本倒しも許す(Kayles)なら長さ 1 を捨てられず、状態数が増える(Day169-Q1)
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| Grundy XOR で判定 | misère でも SG 定理が効くと思う | 状態の全探索(53046/215307 誤答、最小反例 $\{1\}$) |
| 「$G\le1$ のときだけ反転」 | Nim の規則がどのゲームにも効くと思う | 同上(25380/215307 誤答、最小反例 $\{12\}$) |
| 同じ長さの 2 列を消す | $G\oplus G=0$ の持ち込み | 消さない(26570/215307 誤答、最小反例 $\{4,4\}$) |
| 長さ 1 の列を残したまま状態にする | 正解だが遅い | 捨てる(ゲーム木が同型なので正しい) |
| 空の局面を「手番側の負け」にする | 通常ルールの終了条件のまま | 空で手番 = 手番側の勝ち |
次のステップ
- 発展1: Dawson's chess(0.137、1 本倒すと両隣も倒れる)の misère 版を本問に帰着させ、列の長さがどうずれるかを確かめる
- 発展2: 本問の全状態の表から「$X$ と $Y$ を足すと勝敗が変わる局面」を数え、misère 商の考え方(どの列を区別しなければならないか)を体験する
- 発展3: Plambeck の misère 商の原典で 0.07 の商を探し、本問の全探索と照合する(未確認)
検証ログ(本セッションで実行した数値)
- 正規化の照合(実測): 両端がピンで長さ 1〜16 の全 32768 個の 0/1 文字列を、正規化しない全探索と本解で比べて不一致 0
- 誤った方法(実測): 和 40 以下の全分割 215307 局面で、Grundy XOR 53046・Nim 規則の移植 25380・等しい 2 列の消去 26570 局面誤答。和 20 以下(2713 局面)ではそれぞれ 1239・116・237。最小反例は $\{1\}$・$\{12\}$・$\{4,4\}$
- Grundy 数(実測): $G(0..19)=0,0,1,1,2,0,3,1,1,0,3,3,2,2,4,0,5,2,2,3$。$n<160$ で $G(n)\ne G(n+34)$ は $n\in\{0,15,17,32,35,52\}$
- 状態数(実測): 和 40 以下で長さ 2 以上だけの分割は 37337 個、和 50 以下で 204225 個
- 実行時間(実測): 和 50 以下の全 204225 状態を 1 入力で 1.45 s・1.46 s。和 50/55/60 の状態からランダムに $10^5$ 局面で 1.19/2.64/4.39 s(これが和 $\le50$ にした理由)
- 入力例(実測):
First Second Second Second Second First - 扱っていない範囲: misère 商の構成、和 51 以上、周期 34 の文献の例外位置との照合、Dawson・Guy–Smith・Plambeck の原典