問題
$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$ 個のゲームグラフに適用する
概念図
ヒント(クリックで段階的に開示)
ヒント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$ の確認