Day 176-Q1 — 2026-10-12 | 1 人の警官と泥棒(閉路のある対局グラフを後退解析し、最短の捕獲時間を求める)

2026-10-12 赤色 Master (Phase 8+) ★★★★★★★★★ ゲーム理論(焦点帯 M1)。Day161〜175 のゲームはどれも「必ず終わる」ゲームで、Grundy 数や値の和で勝者を決めた。今回は同じ局面に何度でも戻れる追跡ゲーム。メモ化再帰は循環で止まらないので、終局から逆向きに値を確定させる後退解析(retrograde analysis)で解く。並べて、グラフ側の特徴付け(解体可能グラフ)と一致することを確かめる

問題

$N$ 頂点 $M$ 辺の連結な単純無向グラフがある。警官 1 人と泥棒 1 人が次の順に動く。

1. 警官が好きな頂点に立つ 2. 泥棒が、警官の位置を見てから好きな頂点に立つ 3. 以後、警官・泥棒・警官・泥棒…の順に交互に 1 手ずつ指す。1 手では「隣の頂点へ移る」か「その場に留まる」 4. 警官と泥棒が同じ頂点にいた時点で捕獲(警官の勝ち)

両者は互いの位置を常に知っている。警官は捕獲までの警官の手数を最小に、泥棒は最大にしたい(捕まらずに逃げ続けられるなら泥棒の勝ち)。

警官が必ず捕まえられるなら、最適に指したときの警官の手数 $T$ と、そのときの警官の最初の頂点(複数あれば番号最小)を出力せよ。泥棒が逃げ切れるなら -1 を出力せよ。

入力形式

N M
u_1 v_1
...
u_M v_M

出力形式: 捕まえられるなら T c、逃げ切られるなら -1

制約

#制約
1$1\le N\le300$、$N-1\le M\le3000$
2グラフは連結、自己ループ・多重辺なし

入出力例

入力例1

3 2
1 2
2 3

出力例1

1 2

(中央に立てば、泥棒がどちらの端にいても 1 手で捕まえる)

入力例2

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

出力例2

1 5

(4 本の外周 1-2-3-4-1 と、全頂点につながる中心 5。長さ 4 の誘導閉路があるので弦グラフではないが、中心に立てば 1 手で終わる)

入力例3

4 4
1 2
2 3
3 4
4 1

出力例3

-1

(長さ 4 の閉路では、泥棒は常に警官の反対側へ回れる)

入力例4

7 14
1 2
1 3
1 5
1 6
2 3
2 5
2 7
3 4
3 6
3 7
4 5
4 6
4 7
5 7

出力例4

3 2

(半径(ある頂点から全頂点への距離の最大値の最小)は 2 だが、捕獲には 3 手かかる。4 例とも値反復による全探索と一致)

背景と出典

  • 警官と泥棒(cops and robbers): Quilliot(1978、博士論文)と Nowakowski–Winkler(1983, *Discrete Mathematics* 43)が独立に、警官 1 人で捕まえられるグラフ(cop-win グラフ)は解体可能(dismantlable)なグラフと一致することを示した、と私は記憶している(原典は本セッションで未確認)。解体可能とは「閉近傍が他の頂点の閉近傍に含まれる頂点(支配された頂点)を 1 つずつ消して、1 頂点まで減らせる」こと
  • 捕獲時間の上界として、$n\ge7$ の cop-win グラフで $n-4$ という結果(Gavenčiak 2010)を記憶しているが、未確認。本問の検算では $n\le9$ の 474 個の cop-win グラフで捕獲時間の最大が 4($n=8$)だった
  • 後退解析はチェス終盤データベース(Thompson 1986 など)でも使われる、終局から逆向きに勝敗と手数を確定させる方法。本問は自作で、状態 $(c,r,\text{手番})$ が $2N^2$ 個のゲームグラフに適用する

概念図

入力例 2(車輪)と入力例 3(長さ 4 の閉路)、後退解析で値が決まる順序 1 2 3 4 5 警官(青)が中心 5 に立つ どこに泥棒がいても 1 手 → 出力 1 5 1-2-3-4 は誘導閉路 → 弦グラフでない 1 2 3 4 泥棒(赤)は常に対角へ 警官が 2 に来れば泥棒は 3→4 … 支配された頂点が無い → -1 段 0: c = r の局面(両手番とも値 0) 泥棒の自殺手の分だけ cnt を先に減らす 警官手番 C = v+1(行き先が 1 つ確定で決定) 最初に確定した行き先 = 最小(min) 泥棒手番 R = v+1(cnt が 0 で決定) 最後に確定した行き先 = 最大(max) 最後まで決まらない局面 = 逃げ切り 答え min_c max_r C(c, r)、未確定を含む c は不可 値は警官の手でしか増えないので、段ごとのリストで小さい順に確定できる(優先度付きキュー不要)。 状態数 N² × 2、各状態で閉近傍を 1 回ずつ見る: O(N(N+M))。N=300, M=3000 で 0.61 s(実測)。

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

ヒント1(方向性)

局面は(警官の位置, 泥棒の位置, どちらの手番か)の $2N^2$ 通りしかない。ただし同じ局面に戻れるので、普通のメモ化再帰は終わらない。「捕獲済み」の局面から、値が確定した局面を逆向きに広げる。

ヒント2(アプローチ)
  • 警官の手番の局面: 行き先のどれか 1 つが確定すれば、その中で最小の値 +1 で確定する(最初に確定した行き先が最小)
  • 泥棒の手番の局面: 行き先が全部確定したとき、その中の最大で確定する。そのために「まだ確定していない行き先の数」を数えておく

値の小さい順に確定させれば、どちらも 1 回見るだけで済む。

ヒント3(誘導)
  • 終局: $c=r$ の局面は警官手番・泥棒手番とも値 0
  • 段 $v$ で確定した泥棒手番 $(c,r)$ から、警官の前の位置 $c'\in N[c]$ の警官手番 $(c',r)$ を値 $v+1$ で確定
  • 段 $v+1$ で確定した警官手番 $(c,r)$ から、泥棒の前の位置 $r'\in N[r]$ の泥棒手番 $(c,r')$ のカウンタを 1 減らし、0 になったら値 $v+1$ で確定
  • 答えは $\min_c\max_r C(c,r)$。どれかの $r$ で未確定なら、その $c$ からは捕まえられない

模範解答 (Python)

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


def main():
    data = sys.stdin.buffer.read().split()
    n, m = int(data[0]), int(data[1])
    adj = [[v] for v in range(n)]                 # 閉近傍 N[v](その場に留まる手を含む)
    for k in range(m):
        u = int(data[2 + 2 * k]) - 1
        v = int(data[3 + 2 * k]) - 1
        adj[u].append(v)
        adj[v].append(u)
    INF = -1
    # 状態番号 c*n+r。C[s]: 警官の手番で残り何手で捕まえられるか、R[s]: 泥棒の手番
    C = [INF] * (n * n)
    R = [INF] * (n * n)
    cnt = [0] * (n * n)                          # 泥棒の手番で、まだ値の決まらない逃げ先の数
    for c in range(n):
        base = c * n
        for r in range(n):
            cnt[base + r] = len(adj[r])
    level = []                                   # 値 v の泥棒手番状態(R=v で確定)
    for c in range(n):
        s = c * n + c
        C[s] = 0
        R[s] = 0
        level.append(s)
    # 泥棒の逃げ先 r'==c(自分から捕まりに行く手)は C=0 で確定済みなので、カウンタを先に減らしておく
    for c in range(n):
        for r in adj[c]:
            if r != c:
                cnt[c * n + r] -= 1
    v = 0
    while level:
        # level: 値 v で確定した泥棒手番状態 → 警官手番の前の状態は値 v+1
        nxt_cop = []
        for s in level:
            c, r = divmod(s, n)
            for c2 in adj[c]:
                t = c2 * n + r
                if C[t] == INF:
                    C[t] = v + 1
                    nxt_cop.append(t)
        v += 1
        # 値 v で確定した警官手番状態 → 泥棒手番の前の状態のカウンタを減らす。0 になれば R=v
        level = []
        for s in nxt_cop:
            c, r = divmod(s, n)
            for r2 in adj[r]:
                t = c * n + r2
                if R[t] == INF and r2 != c:
                    cnt[t] -= 1
                    if cnt[t] == 0:
                        R[t] = v
                        level.append(t)
    best, best_c = None, -1
    for c in range(n):
        row = C[c * n:(c + 1) * n]
        if INF in row:
            continue
        w = max(row)
        if best is None or w < best:
            best, best_c = w, c
    if best is None:
        print(-1)
    else:
        print(best, best_c + 1)


main()

計算量: 各状態について閉近傍を 1 回ずつ見るので $O(N\cdot(N+M))$、メモリ $O(N^2)$。$N=300,M=3000$ の解体可能グラフ 2 個で 0.61 s、$N=300$ の道で 0.19 s(Python 3.14.7、M1 MacBook Pro 8GB、負荷平均 11〜18 での実測、起動込み)。$N=500,M=5000$ では 2.08 s、$M=20000$ では 7.22 s だったので $N\le300$ とした。

Step-by-Step 解説

1局面と値

局面は $(c,r)$ と手番。$C(c,r)$ を「警官の手番で、あと何手の警官の手で捕まえられるか」、$R(c,r)$ を泥棒の手番での同じ量とする。

$$C(c,r)=1+\min_{c'\in N[c]}R(c',r),\qquad R(c,r)=\max_{r'\in N[r]}C(c,r'),\qquad C(x,x)=R(x,x)=0$$

$N[v]$ は $v$ 自身を含む閉近傍(留まる手)。この連立式は循環していて、上から順に計算できない。

2なぜメモ化再帰では駄目か

$C(c,r)$ を求めるのに $R(c',r)$ が要り、それが $C(c,r)$ 自身を呼ぶことがある(両者が留まれば同じ局面に戻る)。値反復(全局面の式を収束まで繰り返す)は正しいが、1 周 $O(N(N+M))$ を最大 $T$ 周回すので遅い。本問では検算にだけ使った。

3後退解析

ダイクストラ法と同じく、値の小さい順に確定させる。

  • 警官の手番は min なので、行き先が 1 つ確定した時点で(それが最小なので)確定
  • 泥棒の手番は max なので、行き先が全部確定するまで待つ。cnt が 0 になった瞬間に確定し、その値は最後に確定した行き先の値 = 最大

値は警官の手で 1 ずつしか増えないので、段ごとのリスト level で十分(優先度付きキューは要らない)。最後まで確定しない局面は「泥棒が永遠に逃げられる」局面。

4初期配置

警官が先に立ち、泥棒は見てから立つので答えは $\min_c\max_r C(c,r)$。泥棒が $r=c$ に立つことはない(値 0 で損)ので含めても同じ。

5解体可能性との照合

cop-win ⇔ 解体可能、という定理を検算に使う。直観: 葉や「近傍が他の頂点の近傍に含まれる頂点 $u$」に泥棒がいても、$u$ を支配する $v$ にいれば同じことができるので、$u$ を消したグラフで勝てれば元のグラフでも勝てる。逆向き(cop-win なら支配された頂点がある)は、警官が捕まえる直前の局面を考えると出てくる。本問の後退解析の結果(-1 かどうか)は、ランダム 600 グラフで解体可能性の判定と全件一致した。

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

案計算量採否理由
値反復(全局面の式を収束まで回す)$O(T\cdot N(N+M))$検算用のみ正しいが、捕獲時間 $T$ が長い道($T\approx N/2$)で周回が増える
解体可能性の判定(支配された頂点を消す)$O(N^3)$(ビット集合で $N^3/w$)不採用(勝敗だけ)cop-win かどうかは分かるが、捕獲時間と最初の頂点が出ない
弦グラフ(長さ 4 以上の誘導閉路なし)なら cop-win と判定$O(N+M)$不採用(誤り)弦グラフ ⇒ cop-win は正しいが逆は偽。600 グラフ中 68 グラフで誤答(実測)。入力例 2 の車輪が反例
捕獲時間 = 半径(離心率の最小)$O(N(N+M))$不採用(誤り)474 個の cop-win グラフ中 5 個で誤答(実測)。入力例 4 は半径 2・捕獲 3
後退解析(本解)$O(N(N+M))$採用$N=300,M=3000$ で 0.61 s

前提が変わったら

  • 警官が $k$ 人なら状態は $N^{k+1}$ 個になり、$k=2$ でも $N=300$ で $2.7\times10^7$ 状態と重い。警官数(cop number)の判定は一般に難しい($k$ を入力にすると EXPTIME 完全という結果を記憶しているが未確認)
  • 泥棒が速い(1 手で距離 2 まで動ける)なら、泥棒の行き先を「距離 2 以内」に変えるだけで同じ後退解析が使える。解体可能性の定理は使えなくなる
  • 有向グラフや手番の順が違う(泥棒が先に立つ)場合も、局面の定義と初期配置の式を変えるだけで同じ枠組みで解ける

よくあるミス

ミス原因正しい書き方
メモ化再帰で書いて無限再帰になる留まる手で同じ局面に戻る終局から逆向きに確定させる
泥棒の手番を「行き先が 1 つ確定したら確定」にする警官側と同じ処理をコピー泥棒は max なので、全部確定するまで待つ(cnt)
留まる手を入れ忘れる隣接リストだけを使う閉近傍 $N[v]=\{v\}\cup$ 隣接。留まる手を消すと 600 グラフ中 192 グラフで答えが変わる(実測)。最小例は 4 頂点の道で、正しくは 2 2、留まる手なしでは偶奇がずれて -1
泥棒が警官の頂点に動く手をカウンタから減らし忘れるその行き先は「状態」として確定済み前処理で $r'=c$ の分を引く
弦グラフかどうかで判定する十分条件と同値を混同68/600 グラフで誤答(実測)

次のステップ

  • 発展1: 泥棒の速さを 2 にした場合の後退解析を書き、解体可能でも捕まえられないグラフを探す
  • 発展2: 警官 2 人で、ピーターセングラフ(cop number 3)を全探索で確かめる(状態 $10^3\times2$)
  • 発展3: Nowakowski–Winkler(1983)の原典で、解体可能性と捕獲時間の関係を確かめる(本セッションでは未確認)

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

  • 値反復との照合(実測): $N=1\sim9$、辺の確率 0〜0.7 のランダム連結グラフ 600 個(ランダム全域木に辺を足した)。捕獲時間と最初の頂点(番号最小)が全件一致、不一致 0。cop-win 474 個、逃げ切り 126 個
  • 解体可能性との照合(実測): 同じ 600 グラフで「-1 かどうか」と解体可能性の判定が全件一致
  • 誤った方法(実測、同じ 600 グラフ): 弦グラフ判定は 68 グラフ、半径を捕獲時間とする方法は cop-win 474 個中 5 個で誤答。半径が誤る最小例を 20000 グラフから探して入力例 4(7 頂点 14 辺)にした
  • 留まる手を消した版(実測、同じ 600 グラフ): 192 グラフで出力が変わる。最小例は 4 頂点の道(2 2 → -1)
  • 捕獲時間の最大(実測、cop-win のみ): $n=4,5$ で 2、$n=6,7$ で 3、$n=8$ で 4、$n=9$ で 3(ランダム生成なので最悪例ではない)
  • 実行時間(実測、起動込み): $N=300,M=3000$ の解体可能グラフ 0.61 s ×2、$N=300$ の道 0.19 s(答え 150 150)、ランダムグラフ(逃げ切り)0.10 s。$N=500,M=5000$ 2.08 s、$N=1000,M=10000$ 9.35 s
  • 扱っていない範囲: 警官 2 人以上、速い泥棒、原典の確認、捕獲時間の上界 $n-4$ の確認

自己評価

自分の回答

気づき・メモ