Day 161-Q1 — 2026-09-26 — Wythoff のゲーム(負け局面 $(\lfloor k\varphi\rfloor,\lfloor k\varphi^2\rfloor)$ を整数だけで判定し、勝ち手の数を数える)

2026-09-26 赤色 Master (Phase 8+) ★★★★★★★★★ ゲーム理論(焦点帯 M1)。Grundy 数の表を作れない $10^{18}$ 規模で、負け局面の閉じた式を 浮動小数を使わずに 使う

問題

石の山が 2 つあり、個数は $a,b$。2 人が交互に次のどちらかを行う。

  • どちらか一方の山から 1 個以上の好きな数を取る
  • 両方の山から 同じ数 を 1 個以上取る

石を取れなくなった人の負け。

$Q$ 個の局面 $(a_j,b_j)$ それぞれについて、手番の人の 勝ち手の数 を出力せよ。勝ち手とは、指した後の局面が「手番の人が負ける局面」(負け局面)になる手のこと。負け局面なら 0 を出力する。

手は「どの山から(または両方から)何個取るか」で区別する。$a=b$ のとき、左の山から $t$ 個と右の山から $t$ 個は別の手として数える。

入力形式

Q
a_1 b_1
...
a_Q b_Q

制約

#条件
1$1\le Q\le 2\times10^5$
2$0\le a_j,b_j\le 2\times10^{18}$
3入力はすべて整数

入出力例

入力例1

6
0 0
2 3
5 5
4 9
165580140 267914295
1618033988749894848 2618033988749894848

出力例1

0
2
3
1
0
0

($(2,3)$: 両方から 1 個取って $(1,2)$、大きい山を 1 個にして $(2,1)$ の 2 手。$(5,5)$: 左を 3 にする・右を 3 にする・両方から 5 個取る の 3 手。$(4,9)$: 9 を 7 にする 1 手。5 行目は $k=102334155$(フィボナッチ数 $F_{40}$)の負け局面で、int(k * phi) で判定すると 165580141 と 1 ずれて「勝ち局面」と誤る。6 行目は $k=10^{18}$ の負け局面)

Wythoff のゲームは W. A. Wythoff「A modification of the game of Nim」(Nieuw Archief voor Wiskunde 7, 1907)で解かれた。負け局面の小さい方の列は OEIS A000201(下 Wythoff 数列)、大きい方は A001950。本セッションでは原文を参照しておらず、負け局面の式は $200\times200$ の全局面探索と照合して確かめた。

概念図:負け局面の配置

負け局面 (a_k, b_k) と、(5,5) からの 3 つの勝ち手 0 0 5 5 10 10 15 15 20 20 a(小さい山とは限らない) b ■ 負け局面 (a_k, b_k) a_k = ⌊kφ⌋, b_k = a_k + k k=0..12: (0,0) (1,2) (3,5) (4,7) (6,10) (8,13) (9,15) (11,18) … □ (5,5) の勝ち手 3 つ 1) b を partner(5)=3 に → (5,3) 2) a を partner(5)=3 に → (3,5) 3) 両方から 5 個 → (0,0) 各行・各列・各対角線に 負け局面はちょうど 1 つ (1 手で行き来できないため)

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

ヒント1(方向性)

$201\times201$ 程度なら「その局面から負け局面へ行けるか」の DP で全局面を塗れる。負け局面を小さい順に並べると $(0,0),(1,2),(3,5),(4,7),(6,10),(8,13),\dots$。差が $0,1,2,3,\dots$ と 1 ずつ増え、小さい方は「まだ出てきていない最小の数」になっている。

ヒント2(アプローチ)

$k$ 番目の負け局面は $(a_k,b_k)=(\lfloor k\varphi\rfloor,\ \lfloor k\varphi\rfloor+k)$、$\varphi=(1+\sqrt5)/2$。すべての正整数は $a_k$ か $b_k$ のどちらか一方にちょうど 1 回だけ現れる(Beatty の定理)。したがって、どの数 $x$ にも「負け局面での相方」$\mathrm{partner}(x)$ が 1 つだけ決まる。

ヒント3(誘導)

$a\le b$、$d=b-a$ とする。勝ち手の候補は 3 種類で、種類ごとに行き先は 1 つ:

  1. 大きい山を $\mathrm{partner}(a)$ にする($\mathrm{partner}(a)<b$ のとき)
  2. 小さい山を $\mathrm{partner}(b)$ にする($\mathrm{partner}(b)<a$ のとき)
  3. 両方から取って差 $d$ の負け局面 $(a_d,a_d+d)$ にする($a_d<a$ のとき)

$\lfloor k\varphi\rfloor=\lfloor (k+\sqrt{5k^2})/2\rfloor$ は (k + isqrt(5*k*k)) // 2 で正確に計算できる。

模範解答 (Python)

模範解答を表示する(まず自力で解いてから)
import sys
from math import isqrt


def a_of(k):
    """下 Wythoff 数 floor(k*phi)。isqrt は整数平方根なので誤差が出ない。"""
    return (k + isqrt(5 * k * k)) // 2


def partner(x):
    """x を含む負け局面 (a_k, b_k) の相方。x = 0 なら 0。"""
    if x == 0:
        return 0
    k = (isqrt(5 * x * x) - x) // 2          # x = a_k なら k ≈ x/phi
    for kk in (k - 1, k, k + 1, k + 2):
        if kk >= 1 and a_of(kk) == x:
            return x + kk                     # b_k = a_k + k
    k = (3 * x - isqrt(5 * x * x)) // 2      # x = b_k なら k ≈ x/phi^2
    for kk in (k - 1, k, k + 1, k + 2):
        if kk >= 1 and a_of(kk) + kk == x:
            return x - kk
    raise AssertionError(x)                   # Beatty の定理によりここには来ない


def winning_moves(a, b):
    if a > b:
        a, b = b, a
    d = b - a
    ad = a_of(d)
    if ad == a:
        return 0                              # 負け局面
    # 勝ち手は 3 種類で、種類ごとに行き先は 1 つ。a == b のときは山を区別して数える
    c = 0
    if partner(a) < b:
        c += 1                                # 大きい山を partner(a) に
    if partner(b) < a:
        c += 1                                # 小さい山を partner(b) に
    if ad < a:
        c += 1                                # 両方から a - ad 個ずつ
    return c


def main():
    data = sys.stdin.buffer.read().split()
    q = int(data[0])
    out = []
    for i in range(q):
        a = int(data[1 + 2 * i])
        b = int(data[2 + 2 * i])
        out.append(str(winning_moves(a, b)))
    print("\n".join(out))


main()

計算量: 1 クエリあたり isqrt を定数回($O(\log^2 V)$ ビット演算、$V=2\times10^{18}$ で実質定数)。全体 $O(Q)$。実測で $Q=2\times10^5$、値 $\le10^{18}$ の乱数入力に user 1.56 s。

Step-by-Step 解説

1小さい盤面は DP で塗れる

局面 $(a,b)$ が負け ⇔ そこから行けるどの局面も勝ち。$a,b\le200$ の全 40,401 局面を DP で塗ると、負け局面は 153 個($(0,0)$ と、$k=1..76$ の $(a_k,b_k)$ とその左右反転)。この DP は $O(V^3)$ なので $10^{18}$ には使えない。

2負け局面の構造

差 $d=b-a$ ごとに負け局面はちょうど 1 つ(同じ差の局面同士は「両方から取る」手で行き来できるので、2 つあると矛盾する)。さらに同じ数を 2 つの負け局面が共有すると、片方の山から取る手で行き来できてしまう。よって「各差に 1 つ・各数は 1 回だけ」を満たすように小さい順に決まり、それが $(\lfloor k\varphi\rfloor,\lfloor k\varphi^2\rfloor)$ になる($1/\varphi+1/\varphi^2=1$ なので Beatty の定理で 2 列が正整数を分割する)。

3浮動小数では $F_{40}$ で壊れる(実測)

int(k * phi) と正確な値を比べた。最初に食い違う $k$ は 102,334,155(フィボナッチ数 $F_{40}$)。$F_n\varphi$ は $F_{n+1}$ に非常に近い(誤差 $\approx\varphi^{-n}$)ので、double の丸めで整数をまたぐ。無作為に選んだ $k$ での誤り率は $k\sim10^{13}$ で 27/20000、$10^{15}$ で 2082/20000、$10^{18}$ で 19819/20000。
正確にするには $\lfloor k\varphi\rfloor=\lfloor(k+\sqrt{5k^2})/2\rfloor$ とし、$\lfloor\sqrt{5k^2}\rfloor$ を math.isqrt で求める($\sqrt{5k^2}$ は無理数なので、整数部分だけで床関数が決まる)。

4partner(x) の求め方

$x=a_k$ なら $k\approx x/\varphi=x(\sqrt5-1)/2$、$x=b_k$ なら $k\approx x/\varphi^2=x(3-\sqrt5)/2$。どちらも isqrt で近似値を出し、前後数個の $k$ を実際に確かめる。どちらか一方でだけ見つかる(Beatty の定理)。

5勝ち手は 3 種類
  • 大きい山 $b$ から取る → 行き先 $(a,x)$ が負け局面になる $x$ は $\mathrm{partner}(a)$ ただ 1 つ。$x<b$ のときだけ指せる
  • 小さい山 $a$ から取る → $x=\mathrm{partner}(b)<a$ のとき
  • 両方から取る → 差は $d$ のまま。差 $d$ の負け局面は $(a_d,a_d+d)$ だけなので、$a_d<a$ のとき

3 つは別の手(取る山が違う)なので重複はない。行き先の局面が同じでも手としては別($(2,3)$ で「両方から 1」と「右を 1 に」はどちらも $\{1,2\}$ に行くが 2 手)。最初の実装はここを集合で重複除去して 40,401 局面中 186 局面で DP と食い違った。

迷った他案と採らなかった理由

案計算量採否理由
全局面 DP$O(V^3)$検算用のみ$V=200$ で数秒。$10^{18}$ は不可能
負け局面を小さい順に生成して表にする(mex で $a_k$ を決める)$O(V)$不採用閉じた式を知らなくても作れるが、$V=10^{18}$ では表が作れない
int(k * phi)(double)$O(1)$不採用$k=F_{40}\approx10^8$ で最初に誤り、$10^{15}$ で 1 割誤る(実測)
decimal で 50 桁の $\varphi$$O(1)$不採用精度を足せば当面は合うが「何桁あれば十分か」を別途示す必要がある。isqrt は常に正確
Zeckendorf 表現(フィボナッチ進数)で判定$O(\log V)$不採用「$n$ が $a_k$ の形 ⇔ $n$ の Zeckendorf 表現($F_2=1,F_3=2,\dots$ を基数とする)の末尾の 0 が偶数個」という特徴づけがある($n<5000$ で実測一致)。正しいが、相方の計算まで含めると isqrt 版の方が短い
isqrt による整数だけの閉じた式(本解)$O(1)$採用誤差ゼロ。Python の多倍長整数で $2\times10^{18}$ でもそのまま動く

前提が変わったら: C++ で書くなら 5*k*k が 64 ビットを超える($k\sim10^{18}$ で $5\times10^{36}$)ので、__int128 と自作の整数平方根が要る。$k\le 4\times10^8$ 程度なら long double(80 ビット)で足りる場合があるが、境界は環境に依存するので本解のように整数で書くのが安全。

よくあるミス

ミス原因正しい書き方
int(k * (1 + 5 ** 0.5) / 2)double は 53 ビット。$F_{40}$ で最初に誤る(k + isqrt(5 * k * k)) // 2
行き先の局面の集合で重複除去する別の山から取る手は別の手3 種類をそれぞれ独立に数える
$a=b$ のとき大きい山・小さい山を 1 手にまとめる山は区別されるpartner(a) < b と partner(b) < a を両方数える(どちらも真)
$\mathrm{partner}(0)$ を特別扱いしない$0$ は $a_0=b_0=0$ で、Beatty の 2 列には含まれない$x=0$ なら相方 $0$ を返す($(0,b)$ で $b>0$ なら全部取って勝ち)
「差 $d$ の負け局面」を $(a_d, b_d)$ と別に探す差 $d$ の負け局面はちょうど 1 つ(Step 2)a_of(d) 1 回で決まる

次のステップ

  • 発展1: 3 山に一般化した Wythoff のゲームは閉じた式が知られていない。$V\le60$ で全探索し、負け局面の分布を見る
  • 発展2: 「両方から取るとき、取る数の差が $k$ 以下なら可」の一般化(Fraenkel の $a$-Wythoff)で、負け局面が $\lfloor n\alpha\rfloor$ の形になる $\alpha$ を求める
  • 扱わなかった範囲: 各局面の Grundy 数(Wythoff のゲームの Grundy 数は複雑で、和のゲームにすると本問の方法は使えない)、misère 版

検証ログ(本セッションで実行した数値)

  • 全局面 DP との一致(実測): $0\le a,b\le200$ の 40,401 局面で勝ち手の数が全件一致。分布は 0 手 153/1 手 30,812/2 手 7,200/3 手 2,236
  • 初版の誤り(実測): 行き先を集合で重複除去した版は 186 局面で不一致(例 $(2,3)$ で 1、正しくは 2)
  • 浮動小数の誤り(実測): 最初の不一致 $k=102{,}334{,}155=F_{40}$(double 165580141、正 165580140)。各桁 20,000 個の乱択で $10^{12}$ 以下 0 件、$10^{13}$ 27 件、$10^{14}$ 210 件、$10^{15}$ 2,082 件、$10^{16}$ 12,858 件、$10^{18}$ 19,819 件
  • 入力例1 の出力 0 2 3 1 0 0 を実行で確認。5・6 行目を double で判定すると 2 つとも「負け局面でない」と誤る
  • 性能(実測): $Q=2\times10^5$、$a,b\le10^{18}$ の乱数で user 1.56 s(real 4.76 s はメモリの逼迫した開発機のため)。勝ち手 1/2/3 の件数 152,607/36,299/11,094
  • 出所ラベル: 件数・秒数は実測(Python 3.14.7)。負け局面の式と Beatty の定理は文献の結果で、本セッションでは $V\le200$ の範囲でのみ実測照合。Wythoff 1907 と OEIS の原文は本セッションでは参照していない
  • 検証スクリプト: スクラッチパッドの d161/q1.py・q1_test.py

自己評価

自分の回答

気づき・メモ