Day 161-Q2 — 2026-09-26 — Kayles の Grundy 数の周期性($n\ge71$ で周期 12)を使い、長さ $10^{18}$ の列でも勝ち手を数える

2026-09-26 赤色 Master (Phase 8+) ★★★★★★★★★ ゲーム理論(焦点帯 M1)。八進ゲームの Grundy 数を「有限回の計算+周期」で扱い、勝ち手の数え上げを剰余類ごとの個数計算に落とす

問題

ボウリングのピンが $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 数の周期

Kayles の Grundy 数 G(n)(n = 0..95): n ≥ 71 で周期 12 周期部分 4,1,2,8,1,4,7,2,1,8,2,7 0 2 4 6 8 0 12 24 36 48 60 70 84 G(n) ≠ G(n+12) となる例外(全 16 か所、最後は n = 70) 12 個先と同じ値。n ≥ 71 はすべてこちら(n ≤ 3000 で実測) 長い列の勝ち手 = 端(i < 71, r−i < 71)は表引き + 真ん中は i mod 12 の剰余類ごとの個数

ヒント(クリックで段階的に開示)

ヒント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 解説

11 列の Grundy 数

長さ $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$。

2周期は計算で「見つける」(実測)

$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 まで。

3勝ち手の条件

全体の XOR を $X\ne0$ とする。列 $j$ を倒して 2 列に分けた後、全体の XOR が 0 になる ⇔ 分かれた 2 列の XOR が $X\oplus G(L_j)$。これを満たす位置の個数を、1 本倒し(残り $r=L_j-1$)と 2 本倒し($r=L_j-2$)で足す。

4$r$ が大きいときの数え方

$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$ となる個数を足す
5素直な方法との比較(実測)

$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

自己評価

自分の回答

気づき・メモ