問題
ボウリングのピンが $N$ 列に並んでいる。$i$ 列目は $L_i$ 本のピンが一直線に隙間なく並ぶ。2 人が交互に次のどちらかを行う。
- どこかの列のピンを 1 本倒す
- どこかの列の 隣り合う 2 本を倒す
倒したピンは取り除かれ、列はその位置で左右 2 つの列に分かれる(片方または両方が空でもよい)。ピンを倒せなくなった人の負け(Kayles)。
初期局面で手番の人の 勝ち手の数 を出力せよ。勝ち手とは、指した後の局面が手番の人の負け局面になる手。手は「どの列の、どの位置の 1 本(または 2 本)を倒すか」で区別する(左右対称な位置は別の手)。初期局面が負け局面なら 0 を出力する。
入力形式
N
L_1 L_2 ... L_N
制約
| # | 条件 |
|---|---|
| 1 | $1\le N\le 2\times10^5$ |
| 2 | $1\le L_i\le 10^{18}$ |
入出力例
入力例1
1
10出力例1
7入力例2
3
1 2 3出力例2
0入力例3
2
70 1000000000000000000出力例3
666666666666666662(例1: $G(10)=2\ne0$ なので先手必勝。倒した後に左右の Grundy 数の XOR が 0 になる手が 7 通り。例2: $G(1)\oplus G(2)\oplus G(3)=1\oplus2\oplus3=0$ で負け局面。例3: $G(70)=6$ は周期に入る前の最後の例外値。長い列の側で勝ち手が約 $\tfrac23\times10^{18}$ 通りある)
Kayles の Grundy 数が $n\ge71$ で周期 12 になることは R. K. Guy, C. A. B. Smith「The G-values of various games」(Proc. Cambridge Philos. Soc. 52, 1956)の結果として知られる。本セッションでは原文を参照しておらず、$n\le3000$ を計算して「$G(n)\ne G(n+12)$ となる最後の $n$ は 70」を実測で確かめた。
概念図:Grundy 数の周期
ヒント(クリックで段階的に開示)
ヒント1(方向性)
各列は独立したゲームで、全体は和。Sprague–Grundy の定理より、全体の負け局面 ⇔ 各列の Grundy 数 $G(L_i)$ の XOR が 0。長さ $m$ の列から 1 本(2 本)倒すと、長さ $i$ と $m-1-i$($m-2-i$)の 2 列に分かれ、その Grundy 数は $G(i)\oplus G(\cdot)$。
ヒント2(アプローチ)
$G(n)$ を $n=0..3000$ で素直に計算して眺めると、$n\ge71$ で $G(n)=G(n+12)$ が成り立つ。周期部分は $4,1,2,8,1,4,7,2,1,8,2,7$($n\equiv0,1,\dots,11 \pmod{12}$)。勝ち手の条件は「列 $j$ を Grundy 数 $X\oplus G(L_j)$ の状態にする」こと($X$ は全体の XOR)。
ヒント3(誘導)
長さ $r$(倒した後に残るピン数)を $(i,r-i)$ に分ける $i\in[0,r]$ のうち $G(i)\oplus G(r-i)=v$ となる個数を数える関数 count_split(r, v) を作る。$r$ が大きいとき:
- $i<71$ と $r-i<71$ の端: $G(i)$ は表、相手側は周期値。個数は $r \bmod 12$ だけで決まるので前計算できる
- 真ん中 $71\le i\le r-71$: $i \bmod 12$ ごとに $G(i)\oplus G(r-i)$ が定数なので、剰余類ごとの個数を足す
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
PRE = 71 # n >= 71 で G(n) = G(n+12)(Guy–Smith 1956。n<=3000 で実測確認)
PER = 12
SMALL = 400 # r < SMALL は表引きで直接数える
def build(limit):
"""G(0..limit) を定義どおり O(limit^2) で計算。"""
g = [0] * (limit + 1)
for m in range(1, limit + 1):
s = set()
for t in (1, 2): # 1 本 / 隣り合う 2 本
if m >= t:
r = m - t
for i in range(r // 2 + 1): # 左右対称なので半分で足りる
s.add(g[i] ^ g[r - i])
x = 0
while x in s:
x += 1
g[m] = x # mex
return g
g = build(SMALL + PER)
P = [g[PRE + ((c - PRE) % PER)] for c in range(PER)] # P[c] = G(n)(n>=71, n≡c mod 12)
def G(n):
return g[n] if n <= SMALL else P[n % PER]
# small[r][v] = #{i in [0,r] : G(i)^G(r-i) == v}(r < SMALL)
small = [[0] * 16 for _ in range(SMALL)]
for r in range(SMALL):
for i in range(r + 1):
small[r][g[i] ^ g[r - i]] += 1
# edge[c][v] = #{i in [0,70] : G(i)^P[(r-i)%12] == v}(r ≡ c mod 12 かつ r-i >= 71)
edge = [[0] * 16 for _ in range(PER)]
for c in range(PER):
for i in range(PRE):
edge[c][g[i] ^ P[(c - i) % PER]] += 1
def count_split(r, v):
"""長さ r の残りを (i, r-i) に分ける i∈[0,r] のうち G(i)^G(r-i)==v の個数。"""
if v > 15:
return 0
if r < SMALL:
return small[r][v]
c = r % PER
res = 2 * edge[c][v] # i<71 と、左右対称な r-i<71
lo, hi = PRE, r - PRE # 両側とも周期部分
for d in range(PER):
if P[d] ^ P[(c - d) % PER] == v:
first = lo + ((d - lo) % PER) # [lo,hi] で i≡d となる最小の i
if first <= hi:
res += (hi - first) // PER + 1
return res
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
L = list(map(int, data[1:1 + n]))
x = 0
for l in L:
x ^= G(l)
if x == 0:
print(0)
return
total = 0
for l in L:
want = x ^ G(l) # この列を Grundy 数 want の状態にすれば全体の XOR が 0
total += count_split(l - 1, want) # 1 本倒す
if l >= 2:
total += count_split(l - 2, want) # 隣り合う 2 本を倒す
print(total)
main()
計算量: 前計算 $O(\text{SMALL}^2)$(約 0.1 s)、1 列あたり $O(12)$。全体 $O(\text{SMALL}^2+12N)$。実測で $N=2\times10^5$、$L_i\le10^{18}$ の乱数入力に user 0.50 s。
Step-by-Step 解説
長さ $m$ の列から 1 本倒すと $(i,m-1-i)$、2 本倒すと $(i,m-2-i)$ の 2 列に分かれる。2 列の和の Grundy 数は XOR なので
$$G(m)=\mathrm{mex}\{G(i)\oplus G(m-1-i)\}\cup\{G(i)\oplus G(m-2-i)\}$$
これは八進ゲーム 0.77 の定義そのもの。$G(0..20)=0,1,2,3,1,4,3,2,1,4,2,6,4,1,2,7,1,4,3,2,1$。
$n\le3000$ を計算(0.30 s)して $G(n)\ne G(n+12)$ となる $n$ を全部挙げると $0,3,10,11,15,16,18,21,27,28,34,39,45,57,58,70$。最後が 70 で、以降 3000 まで例外は無い。$G$ の最大値は 8。
八進ゲームには「周期性の定理」(Guy–Smith)があり、分割が 2 つまでのゲームでは、ある $n_0$ と周期 $p$ について $n_0\le n< 2n_0+p+t$($t$ は 1 回に取る最大数、Kayles は 2)の範囲で周期が成り立てば、以降ずっと成り立つ。Kayles は $n_0=71,p=12,t=2$ なので、$n<156$ まで確認すれば証明になる。本問の実測は 3000 まで。
全体の XOR を $X\ne0$ とする。列 $j$ を倒して 2 列に分けた後、全体の XOR が 0 になる ⇔ 分かれた 2 列の XOR が $X\oplus G(L_j)$。これを満たす位置の個数を、1 本倒し(残り $r=L_j-1$)と 2 本倒し($r=L_j-2$)で足す。
$i\in[0,r]$ を 3 つに分ける。
- $i\le70$: $G(i)$ は表、$G(r-i)=P[(r-i)\bmod12]$。個数は $r\bmod12$ と $v$ だけで決まる →
edge[c][v] - $r-i\le70$: 左右対称なので同じ個数($r\ge400$ なので両端は重ならない)
- $71\le i\le r-71$: $G(i)\oplus G(r-i)=P[i\bmod12]\oplus P[(r-i)\bmod12]$ は $i\bmod12$ だけで決まる。条件を満たす剰余類 $d$ ごとに、区間内で $i\equiv d$ となる個数を足す
$G$ を定義どおり計算するのは $O(n^2)$ で、$n=10^4$ で 4.47 s。$10^{18}$ は論外。周期を使えば $G(n)$ は $O(1)$、勝ち手の数え上げも剰余類 12 個で済む。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 全局面探索(列の多重集合を状態に) | 指数 | 検算用のみ | 総ピン数 13 以下の 384 ケースで本解と照合 |
| $G$ を定義どおり計算 | $O(L^2)$ | 不採用 | $L=10^4$ で 4.47 s(実測)。制約の $10^{18}$ は不可能 |
| 周期の開始点を決め打ちせず、実行時に周期を検出 | 前計算 $O(n_0^2)$ | 不採用 | 一般の八進ゲームに使える方法だが、本問は Kayles 固定なので定数で十分。周期性の定理の検証範囲($n<156$)を満たす |
| 勝ち手を $i$ について 1 個ずつ調べる | $O(L)$ | 不採用 | $L=10^{18}$ で不可能。周期部分は剰余類ごとの個数で足す |
| 前計算+剰余類の個数(本解) | $O(12N)$ | 採用 | $N=2\times10^5$ で 0.50 s(実測) |
前提が変わったら:
- misère 版(最後に倒した人の負け)では Grundy 数の XOR で判定できない。Kayles の misère 版は Sibert–Conway(1992)が解いたとされる(本セッションでは原文未参照)が、本問の方法はそのままでは使えない
- Dawson's Kayles(隣り合う 2 本だけ倒せる、八進ゲーム 0.07)も同じ方法で扱える。本セッションの定義(長さ $m$ から 2 本除き 2 列に分ける)で計算すると周期 34、$G(n)\ne G(n+34)$ の最後の $n$ は 52(文献の添字の付け方によって 1 ずれる)
- 八進ゲームには周期性がまだ分かっていないもの(0.007 など。文献の記述で本セッションでは未確認)もある。周期を前提にする前に、上の定理の範囲まで計算して確かめる
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 周期を $n\ge0$ からと思い込み $G(n)=P[n\bmod12]$ を全域で使う | $n\le70$ に例外が 16 か所ある | $n\le$ 表の範囲は表、以降だけ周期 |
| 左右対称な位置を 1 手にまとめる | 問題は位置で手を区別する | $i\in[0,r]$ 全体を数える(build の半分ループは mex 用なので別物) |
| 端の $i<71$ と $r-i<71$ を二重に数える/数え落とす | $r$ が小さいと両端が重なる | $r<400$ は表で直接数え、大きい $r$ だけ 3 分割 |
| 2 本倒す手を $L=1$ の列でも数える | 残りが負になる | if l >= 2 |
| $X=0$ のときも数える | 負け局面ではどの手も勝ち手でない | 先に $X=0$ なら 0 を出力 |
次のステップ
- 発展1: Guy–Smith の周期性の定理を自分で証明し、$n<156$ までの確認で十分な理由を説明する
- 発展2: 任意の八進ゲームのコード(例 0.137 = Dawson's Chess)を入力に取り、周期を自動検出して同じ問題を解く
- 扱わなかった範囲: misère Kayles、ピンが円形に並ぶ場合(最初の 1 手で 1 列になる)
検証ログ(本セッションで実行した数値)
- 周期(実測): $n\le3000$ の $G$ を 0.30 s で計算。$G(n)\ne G(n+12)$ となる $n$ は $\{0,3,10,11,15,16,18,21,27,28,34,39,45,57,58,70\}$、最大値 8、周期部分 $[4,1,2,8,1,4,7,2,1,8,2,7]$($n\equiv0..11$)
- 全局面探索との一致(実測): 列 1〜3 本・総ピン数 13 以下のランダム 384 ケースで勝ち手の数が全件一致
- 素直な数え上げとの一致(実測): $L_i\le2900$ の $G$ 表を使った $O(L)$ の数え上げと 500 ケース一致、プログラム全体(標準入出力)で 300 ケース一致
- 入力例 1〜3 の出力
7、0、666666666666666662を実行で確認 - 性能(実測): $N=2\times10^5$、$L_i\le10^{18}$ で user 0.50 s(出力 501004)。$G$ を定義どおり $n=10^4$ まで計算すると 4.47 s
- Dawson's Kayles(実測・参考): 本セッションの定義で $G(n)\ne G(n+34)$ となる $n$ は $\{0,15,17,32,35,52\}$
- 出所ラベル: 数値は実測(Python 3.14.7)。周期性の定理と Guy–Smith 1956 は文献の結果で、本セッションでは原文を参照していない
- 検証スクリプト: スクラッチパッドの
d161/q2.py・q2_grundy.py・q2_test.py