Day 013-Q2 — Algorithmic Game Theory(複合ゲーム)

2026-04-26 赤色 / Phase 8 ★★★★★★★★ Sprague-Grundy

問題

N 個の独立なゲームが同時進行。各ゲーム i は DAG $G_i$ で表され、駒の位置 $v_i$ から2人が交互に動かす。動かせなくなったら負け。全ゲームの Grundy 値の XOR が 0 でないとき先手必勝。Grundy 値、勝者、先手必勝なら初手を出力せよ。

制約

$1 \le N \le 10$
$V_i \le 100$, $E_i \le 300$
各グラフは DAG

入出力例

入力例 1

3
3 2 1
1 2
1 3
4 3 2
2 3
2 4
3 4
5 4 1
1 2
1 3
2 4
3 5

出力例 1

Grundy values: 1 2 2
XOR: 1
First player wins
Move in game 1 to vertex 2

ヒント (段階的開示)

ヒント1: 方向性
Sprague-Grundy 定理: 複合ゲームの Grundy 値 = 各独立ゲームの Grundy 値の XOR。
ヒント2: アプローチ
$g(v) = \text{mex}(\{g(w) | v \to w\})$。XOR ≠ 0 のとき、target = XOR ^ g_i に変えられる移動を探す。
ヒント3: 誘導
メモ化再帰で全頂点の Grundy 値を計算。集合サイズが k なら mex ≤ k。

模範解答 (Python)

import sys
from functools import lru_cache
input = sys.stdin.readline

def solve():
    N = int(input())
    games = []
    for _ in range(N):
        V, E, s = map(int, input().split())
        graph = [[] for _ in range(V + 1)]
        for _ in range(E):
            u, v = map(int, input().split())
            graph[u].append(v)
        games.append((V, s, graph))

    def compute_grundy(graph, V):
        memo = {}
        def grundy(v):
            if v in memo:
                return memo[v]
            reachable = set()
            for w in graph[v]:
                reachable.add(grundy(w))
            m = 0
            while m in reachable:
                m += 1
            memo[v] = m
            return m
        all_g = {}
        for v in range(1, V + 1):
            all_g[v] = grundy(v)
        return all_g

    grundy_vals = []
    all_g_maps = []
    for V, s, graph in games:
        g_map = compute_grundy(graph, V)
        grundy_vals.append(g_map[s])
        all_g_maps.append(g_map)

    total_xor = 0
    for g in grundy_vals:
        total_xor ^= g

    print(f"Grundy values: {' '.join(map(str, grundy_vals))}")
    print(f"XOR: {total_xor}")

    if total_xor == 0:
        print("Second player wins")
    else:
        print("First player wins")
        for i, (gv, (V, s, graph), g_map) in enumerate(zip(grundy_vals, games, all_g_maps)):
            target = total_xor ^ gv
            for w in graph[s]:
                if g_map[w] == target:
                    print(f"Move in game {i+1} to vertex {w}")
                    return

solve()

Step-by-Step 解説

1Sprague-Grundy 定理
複合ゲームの全体 Grundy = 各ゲームの XOR。XOR ≠ 0 で先手必勝。
2DAG 上の Grundy 値
$g(v) = \text{mex}(\{g(w)\})$。終端は $g=0$。メモ化再帰で O(V+E)。
3最適初手の探索
target = total_xor ^ g_i に変えられる出力辺を探す。
4mex の高速計算
0 から順に確認、O(k)。

よくあるミス

ミス原因正しい書き方
合成で sum 使用Nim 値は XOR 合成total ^= g
終端 Grundy を 1mex({}) = 0出力辺なし → g=0
target の計算ミスtarget = total_xor ^ g_i

次のステップ

  • DAG でない循環ゲームへの対応

自己評価

自分の回答

気づき・メモ