問題
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 で先手必勝。
複合ゲームの全体 Grundy = 各ゲームの XOR。XOR ≠ 0 で先手必勝。
2DAG 上の Grundy 値
$g(v) = \text{mex}(\{g(w)\})$。終端は $g=0$。メモ化再帰で O(V+E)。
$g(v) = \text{mex}(\{g(w)\})$。終端は $g=0$。メモ化再帰で O(V+E)。
3最適初手の探索
target = total_xor ^ g_i に変えられる出力辺を探す。
target = total_xor ^ g_i に変えられる出力辺を探す。
4mex の高速計算
0 から順に確認、O(k)。
0 から順に確認、O(k)。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 合成で sum 使用 | Nim 値は XOR 合成 | total ^= g |
| 終端 Grundy を 1 | mex({}) = 0 | 出力辺なし → g=0 |
| target の計算ミス | target = total_xor ^ g_i |
次のステップ
- DAG でない循環ゲームへの対応