Day 173-Q3 — 2026-10-09 | 無向頂点ジオグラフィ(勝敗は「全ての最大マッチングに入るか」で決まる)

2026-10-09 赤色 Master (Phase 8+) ★★★★★★★★★ ゲーム理論(焦点帯 M1)。これまでのゲーム(Nim 系・Hackenbush・Kayles)は Grundy 数や値を足して解いた。今回は状態が「訪れた頂点の集合」で指数個あり、和にも分かれない。それでも二部グラフなら最大マッチング 1 回と BFS 2 回で全始点の勝敗が出る

問題

左側 $N_L$ 頂点・右側 $N_R$ 頂点の二部グラフがあり、辺は $M$ 本ある。辺 $i$ は左の頂点 $a_i$ と右の頂点 $b_i$ を結ぶ。

ある頂点 $v$ に駒を置き、$v$ を「訪問済み」にする。先手から交互に、駒を隣接する未訪問の頂点へ動かし、動かした先を訪問済みにする。動かせなくなった人の負け。

すべての頂点 $v$ について、$v$ から始めたときに先手と後手のどちらが勝つかを求めよ(両者最善)。

入力形式

N_L N_R M
a_1 b_1
...
a_M b_M

出力形式: $N_L+N_R$ 行。まず左の頂点 $1..N_L$、続いて右の頂点 $1..N_R$ の順に、先手が勝つなら First、後手が勝つなら Second。

制約

#条件
1$1\le N_L,N_R\le 10^5$
2$0\le M\le 2\times10^5$
3$1\le a_i\le N_L$、$1\le b_i\le N_R$。多重辺はない

入出力例

入力例1

2 2 3
1 1
2 1
2 2

出力例1

First
First
First
First

(左1−右1−左2−右2 の 4 頂点の路。どこから始めても先手勝ち)

入力例2

1 3 3
1 1
1 2
1 3

出力例2

First
Second
Second
Second

(星。中心から始めれば先手は葉へ動いて勝つ。葉から始めると先手は中心へ動くしかなく、後手が別の葉へ動いて勝つ)

入力例3

3 2 4
1 1
2 1
2 2
3 2

出力例3

Second
Second
Second
First
First

(左1−右1−左2−右2−左3 の 5 頂点の路。3 例とも全状態のミニマックス探索と一致)

背景と出典

無向グラフ上の頂点ジオグラフィ(Undirected Vertex Geography)について、「$v$ から始めて先手が勝つ ⇔ $v$ がグラフのすべての最大マッチングに含まれる」という定理がある。Fraenkel・Scheinerman・Ullman(1993, *Undirected edge geography*, Theoretical Computer Science)で示された、と私は記憶している(原典は本セッションでは確認していない。定理そのものは一般グラフ 2011 頂点ぶんの全探索で照合した。下の検証ログ)。

有向グラフ版のジオグラフィは PSPACE 完全として知られる、と私は記憶している(同じく未確認)。無向にすると多項式時間で解けるのは、相手の手に「マッチングの相手へ動く」で応じ続ける戦略があるから。

全最大マッチングに含まれる頂点の判定は、Day148-Q3 の Dulmage–Mendelsohn 分解と同じ交互路の到達で求まる。本問は自作。

概念図

入力例 3: 左1−右1−左2−右2−左3 の路。太線 = 求めた最大マッチング M 左1 Second 右1 First 左2 Second 右2 First 左3 Second 左の自由頂点 左3 から交互路(非 M → M)で 左2・左1 に着く 着いた左の頂点 = M を入れ替えると自由にできる = ある最大マッチングに入らない → Second 右の自由頂点は無いので、右1・右2 はすべての最大マッチングに入る → First 先手の戦略: いつも「今の頂点の M の相手」へ動く。相手が動いた先も必ず M で覆われている

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

ヒント1(方向性)

状態は「駒の位置+訪問済み集合」で指数個。小さいグラフで勝敗を全探索し、「先手が勝つ頂点」に共通する性質を探す。次数や、ある 1 つのマッチングで使われているかでは決まらない。

ヒント2(アプローチ)

最大マッチング $M$ が $v$ を含むとき、先手は「いつも駒のいる頂点の $M$ の相手へ動く」とする。後手が動いた先がいつも $M$ で覆われていれば先手は動き続けられる。覆われていない頂点に着いたら、路を反転させると $v$ を含まない最大マッチングが作れる。だから「すべての最大マッチングに $v$ が入る」が先手勝ちの条件になる。

ヒント3(誘導)

二部グラフで最大マッチング $M$ を 1 つ求める。左の頂点 $u$ が「ある最大マッチングで使われない」⇔ 左の自由頂点から、左→右は任意の辺、右→左は $M$ の辺でたどって $u$ に着く。右側も同様に右の自由頂点から BFS。着いた頂点は Second、着かない頂点は First。

模範解答 (Python)

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


def hopcroft_karp(nl, nr, adj):
    INF = 1 << 30
    ml = [-1] * nl
    mr = [-1] * nr
    while True:
        dist = [INF] * nl
        q = deque()
        for u in range(nl):
            if ml[u] < 0:
                dist[u] = 0
                q.append(u)
        found = False
        while q:
            u = q.popleft()
            for v in adj[u]:
                w = mr[v]
                if w < 0:
                    found = True
                elif dist[w] == INF:
                    dist[w] = dist[u] + 1
                    q.append(w)
        if not found:
            return ml, mr
        it = [0] * nl
        for s in range(nl):                      # 反復 DFS で増加路を探す
            if ml[s] >= 0:
                continue
            stack = [s]
            while stack:
                u = stack[-1]
                if it[u] < len(adj[u]):
                    v = adj[u][it[u]]
                    it[u] += 1
                    w = mr[v]
                    if w < 0:                    # 増加路が見つかった: スタックに沿って反転
                        while stack:
                            uu = stack.pop()
                            vv = adj[uu][it[uu] - 1]
                            ml[uu] = vv
                            mr[vv] = uu
                        break
                    if dist[w] == dist[u] + 1:
                        stack.append(w)
                else:
                    dist[u] = INF
                    stack.pop()


def main():
    data = sys.stdin.buffer.read().split()
    nl, nr, m = int(data[0]), int(data[1]), int(data[2])
    adj = [[] for _ in range(nl)]
    radj = [[] for _ in range(nr)]
    p = 3
    for _ in range(m):
        a, b = int(data[p]) - 1, int(data[p + 1]) - 1
        p += 2
        adj[a].append(b)
        radj[b].append(a)
    ml, mr = hopcroft_karp(nl, nr, adj)
    # 左の頂点 u が「ある最大マッチングで使われない」
    #   ⇔ 左の自由頂点から 交互路(左→右は非マッチ辺、右→左はマッチ辺)で偶数歩で着く
    freeL = [False] * nl
    q = deque(u for u in range(nl) if ml[u] < 0)
    for u in q:
        freeL[u] = True
    while q:
        u = q.popleft()
        for v in adj[u]:
            w = mr[v]
            if w >= 0 and not freeL[w]:
                freeL[w] = True
                q.append(w)
    freeR = [False] * nr
    q = deque(v for v in range(nr) if mr[v] < 0)
    for v in q:
        freeR[v] = True
    while q:
        v = q.popleft()
        for u in radj[v]:
            w = ml[u]
            if w >= 0 and not freeR[w]:
                freeR[w] = True
                q.append(w)
    out = ['Second' if f else 'First' for f in freeL]
    out += ['Second' if f else 'First' for f in freeR]
    sys.stdout.write('\n'.join(out) + '\n')


if __name__ == "__main__":
    main()

計算量: Hopcroft–Karp が $O(M\sqrt{N})$、BFS 2 回が $O(N+M)$。$N_L=N_R=10^5$、$M=2\times10^5$ のランダムなグラフで 2.0 s(Python 3.14.7、M1 MacBook Pro、負荷平均 23〜27 の混んだ時間帯での実測、起動と入出力を含む)。

Step-by-Step 解説

1全探索で見えること

状態は(駒の位置、訪問済み集合)。小さいグラフなら lru_cache で勝敗が出る。そこで「次数が正なら先手勝ち」と比べると 4153 頂点中 1382 頂点で外れ、「求めた 1 つの最大マッチングで使われていれば先手勝ち」でも 729 頂点で外れる(実測)。どちらでもない。

2先手の戦略($v$ が全最大マッチングに入るとき)

最大マッチング $M$ を 1 つ固定する($v$ は必ず覆われている)。先手はいつも「今の頂点の $M$ の相手」へ動く。後手がどこかへ動いたとき、そこが $M$ で覆われていて相手が未訪問なら先手はまた動ける。

もし後手が $M$ で覆われていない頂点 $x$ に動けたとすると、ここまでの道 $v\to v_1\to\dots\to x$ は「$M$ の辺・非 $M$ の辺」が交互に並び、$x$ が自由頂点なので、この道に沿って $M$ を入れ替えると大きさが同じで $v$ を含まない最大マッチングができる。これは仮定に反する。相手が訪問済みのケースも、道が交互になっていることから起こらない。よって先手は必ず動ける=先手勝ち。

3後手の戦略($v$ を含まない最大マッチング $M'$ があるとき)

$M'$ では $v$ は自由。先手がどこへ動いても、その先 $y$ は $M'$ で覆われている(覆われていなければ辺 $vy$ を $M'$ に足せて最大性に反する)。後手は $y$ の $M'$ の相手へ動く。以下 Step 2 と同じ議論で、後手はいつも動ける=後手勝ち。

4「すべての最大マッチングに入る」を BFS で判定

二部グラフで最大マッチング $M$ を 1 つ求める。左の頂点 $u$ を含まない最大マッチングがあるのは、$u$ が自由か、左の自由頂点 $f$ から「左→右(非 $M$ 辺)→左($M$ 辺)」をくり返して $u$ に着けるとき(その偶数長の交互路を反転すれば $u$ が自由になる)。右側も右の自由頂点から同じように BFS。

5実装上の注意

Hopcroft–Karp の DFS は再帰にすると深さが $10^5$ を超えうるので、スタックで書いた。増加路が見つかったら、スタックに積まれた左頂点ごとに「直前にたどった辺」adj[u][it[u]-1] でマッチを張り替える。

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

案計算量採否理由
全状態のミニマックス$O(2^N N)$検算用のみ12 頂点まで。4153 頂点の照合に使用
頂点ごとに $v$ を消して最大マッチングを取り直す($\nu(G-v)<\nu(G)$ か)$O(N\cdot M\sqrt N)$不採用一般グラフでも使える判定だが、$N=2\times10^5$ では $10^5$ 回のマッチングになる。一般グラフ版の定理の照合(ビット DP のマッチング)にだけ使った
一般グラフで Edmonds の花法+Gallai–Edmonds 分解$O(N^3)$ など不採用(範囲外)Gallai–Edmonds 分解の $D$(ある最大マッチングで自由になれる頂点の集合)を求めれば $v\notin D$ で判定できるが、花法の実装が要る。本セッションでは実装しておらず、問題を二部グラフに限った
最大マッチング 1 回+交互路 BFS 2 回(本解)$O(M\sqrt N)$採用2.0 s

前提が変わったら:

  • 一般グラフなら、定理はそのまま成り立つ(ビット DP で 2011 頂点を照合)が、判定には花法が要る。Gallai–Edmonds 分解で「ある最大マッチングで自由になれる頂点の集合 $D$」を求め、$v\notin D$ なら First
  • 辺を訪問済みにする版(Undirected Edge Geography)は別の問題で、本解は使えない
  • 駒が 2 つ、または和のゲームにすると、勝敗だけでなく Grundy 数が要る。マッチングの議論は Grundy 数を教えてくれない(本セッションでは未検討)

よくあるミス

ミス原因正しい書き方
「求めた最大マッチングで使われていれば先手勝ち」最大マッチングは 1 つではない4153 頂点中 729 頂点で誤答(実測)。「すべての最大マッチングに入るか」を交互路で判定する
次数が正なら先手勝ち1 手目は打てても、その後の応酬で負ける4153 頂点中 1382 頂点で誤答(実測)
左の自由頂点からの BFS で右の頂点にも印を付ける右の頂点が「自由になれるか」は右の自由頂点からの交互路で決まる左右それぞれ、自分の側の自由頂点から BFS する
BFS の右→左で任意の辺をたどる交互路でなくなる右→左はマッチ辺 mr[v] だけ
Hopcroft–Karp の DFS を再帰で書く深さ $10^5$ 超で落ちるスタックで書く

次のステップ

  • 発展1: 一般グラフ版(Edmonds の花法+Gallai–Edmonds 分解)
  • 発展2: 辺を訪問済みにする版(Undirected Edge Geography)
  • 発展3: Fraenkel–Scheinerman–Ullman(1993)の原典を確認する(本セッションでは未確認)

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

  • 全状態ミニマックスとの照合(実測): 左右 1〜6 頂点・辺の確率 0.2〜0.8 のランダム二部グラフ 600 個、計 4153 頂点で不一致 0(First 1769 頂点)
  • 定理そのものの照合(実測): 1〜9 頂点のランダム一般グラフ 400 個、計 2011 頂点で「ミニマックスの勝敗」と「$\nu(G-v)<\nu(G)$(ビット DP の最大マッチング)」が全一致
  • 誤った方法(実測): 「次数が正なら先手勝ち」は 1382/4153 頂点、「求めた 1 つの最大マッチングで使われていれば先手勝ち」は 729/4153 頂点で誤答
  • 入力例(実測): 3 例とも上の出力と一致
  • 実行時間(実測、起動と入出力込み、負荷平均 23〜27): $N_L=N_R=10^5,M=2\times10^5$ で 2.0 s、$M=4\times10^5$ で 6.99 s、$N_L=N_R=2\times10^5,M=4\times10^5$ で 5.27 s。後者 2 つのため制約を $M\le2\times10^5$ にした
  • 扱っていない範囲: 一般グラフ(花法)、辺ジオグラフィ、和のゲーム、原典の確認

自己評価

自分の回答

気づき・メモ