問題
頂点 $1..N$、辺 $M$ 本の連結な単純無向グラフで、警官 1 人と泥棒 1 人が遊ぶ。
- 警官が頂点を 1 つ選んで置く。次に泥棒が警官のいない頂点を選んで置く
- 以後、次を交互に繰り返す
- 警官の手番: その場に留まるか、隣の頂点へ 1 歩動く。泥棒のいる頂点に入ったら捕獲
- 泥棒の手番: 長さ $0$ 以上 $S$ 以下の歩道に沿って動く(長さ 0 は留まる)。終点は警官のいる頂点であってはならない。さらに
- $P=0$ のとき: 歩道の途中の頂点にも警官のいる頂点を含めてはならない(通り抜け禁止)
- $P=1$ のとき: 途中で警官のいる頂点を通り抜けてよい
両者は互いの位置をすべて知っている。警官は捕獲までの警官の手数を最小に、泥棒は最大にする。両者が最善を尽くしたときの手数を出力せよ(最初の配置は手数に数えない)。泥棒が永遠に逃げ切れるなら -1 を出力せよ。$N=1$ なら 0。
入力形式
N M S P
a_1 b_1
...
a_M b_M
制約
| # | 制約 |
|---|---|
| 1 | $1\le N\le500$、$N-1\le M\le2\times10^4$ |
| 2 | $1\le S\le3$、$P\in\{0,1\}$ |
| 3 | グラフは連結で、自己ループ・多重辺はない |
入出力例
入力例1
4 3 3 0
1 2
2 3
3 4出力例1
2(4 頂点の道。警官は 2 に置き、泥棒は端の 4 に置く。警官が 3 へ進むと、泥棒は警官を通れないので 4 から動けず、次の手で捕まる。木では通り抜けが禁止なら速さに関係なく警官が勝つ)
入力例2
4 3 3 1
1 2
2 3
3 4出力例2
-1(同じ道で通り抜けを許す。警官が 3 に来たら泥棒は 4→3→2→1 と警官を通り抜けて反対の端へ行き、警官から距離 2 の位置に立つ。警官が 2 に来れば 1→2→3→4 と戻る。速さ 2 だと通り抜けた先が警官の隣になるので、同じ入力で $S=2$ なら答えは 2)
入力例3
6 9 3 0
1 2
1 3
1 4
1 6
2 3
2 6
3 4
3 5
4 5出力例3
-1(通り抜け禁止でも、速さ 3 なら逃げ切れる。同じグラフで $S=1,2$ なら答えは 2。6 頂点以下のラベル付き連結グラフを全部調べて、「速さ 2 なら警官の勝ち、速さ 3(通り抜けなし)
背景と出典
- 速い泥棒の警官と泥棒は Fomin, Golovach, Kratochvíl, Nisse, Suchan「Pursuing a fast robber on a graph」(2010) と、Chalopin, Chepoi, Nisse, Vaxès「Cop and robber games when the robber can hide and ride」(2011) で扱われていると記憶している(どちらも本文は未確認)。そこで泥棒は警官のいる頂点を通れない定義だったと記憶しているが、確かめていない
- 速さ 1 で警官が勝つグラフは解体可能グラフ(Nowakowski–Winkler 1983/Quilliot 1978、原典未確認)。Day176-Q1 で後退解析と全件一致を確認した
- 本問の「通り抜けてよい」版・入力例・制約は自作。入力例 3 は本セッションの全列挙で見つけた
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
局面は(警官の位置, 泥棒の位置, 手番)だけで決まる。Day176-Q1・Day177-Q2 と同じく、捕獲が確定した局面から逆向きに広げる後退解析が使える。速さ・通り抜けの規則が変えるのは「泥棒が 1 手で行ける頂点の集合」だけ。
ヒント2(アプローチ)
警官が $c$ にいるとき、泥棒が $r$ から 1 手で行ける集合 $\mathrm{Mv}_c(r)$ を先に全部作る。
- 通り抜け禁止: グラフから $c$ を消して、$r$ から距離 $S$ 以内
- 通り抜け可: 元のグラフで $r$ から距離 $S$ 以内、から $c$ を除く
どちらも「$r'\in\mathrm{Mv}_c(r)\iff r\in\mathrm{Mv}_c(r')$」と対称なので、逆向きの辺も同じ集合で引ける。
ヒント3(誘導)
- $T(c,r)$ を「警官の手番で警官 $c$・泥棒 $r$ のときの残り手数」とする。$r$ が $c$ の隣なら 1
- 泥棒の手番 $(c',r)$ の値は $\max_{r'\in\mathrm{Mv}_{c'}(r)}T(c',r')$。行き先が全部確定した段で値が決まる
- 警官の手番 $(c,r)$ は、$c$ の隣か $c$ 自身の $c'$ のうち、泥棒の値が最初に確定したもので $T=$ その値 $+1$
- 集合を整数のビット列で持てば「行き先が全部確定したか」は
mv[c][r] & ~done[c] == 0の 1 回で判定できる
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def solve(n, edges, s, passing):
adj = [0] * n
nbr = [[] for _ in range(n)]
for u, v in edges:
adj[u] |= 1 << v; adj[v] |= 1 << u
nbr[u].append(v); nbr[v].append(u)
full = (1 << n) - 1
# mv[c][r]:警官が c にいるとき、泥棒が r から 1 手で行ける頂点(留まるを含む)
mv = []
if passing:
ball = [1 << v for v in range(n)]
for _ in range(s):
ball = grow(ball, nbr)
for c in range(n):
nc = ~(1 << c)
mv.append([b & nc for b in ball])
else:
for c in range(n):
ball = [1 << v for v in range(n)]
ball[c] = 0
for _ in range(s):
ball = grow(ball, nbr)
ball[c] = 0
mv.append(ball)
done = [0] * n # done[c]:T(c, r) が確定した r の集合
knownR = [1 << c for c in range(n)] # 泥棒の手番 (c, r) で値が確定した r(r=c は無い局面)
new = [adj[c] for c in range(n)] # T = 1:隣の泥棒をそのまま捕まえる
t = 1
maxT = [0] * n
while any(new):
for c in range(n):
if new[c]:
done[c] |= new[c]
if done[c] | (1 << c) == full:
maxT[c] = t
# 泥棒の手番 (c', r):行き先がすべて確定したら値 t
newR = [0] * n
for c in range(n):
if not new[c]:
continue
d = done[c]
unk = full & ~knownR[c]
row = mv[c]
x = unk
got = 0
while x:
low = x & -x
r = low.bit_length() - 1
if row[r] & ~d == 0:
got |= low
x ^= low
knownR[c] |= got
newR[c] = got
# 警官の手番 (c, r):c の隣か c 自身の c' で泥棒の値が t なら T = t + 1
nxt = [0] * n
for cp in range(n):
g = newR[cp]
if g:
nxt[cp] |= g
for c in nbr[cp]:
nxt[c] |= g
new = [nxt[c] & ~done[c] & ~(1 << c) for c in range(n)]
t += 1
if n == 1:
return 0
best = -1
for c in range(n):
if done[c] | (1 << c) == full and (best < 0 or maxT[c] < best):
best = maxT[c]
return best
def grow(ball, nbr):
"""半径を 1 増やす:自分の球と隣の球の和"""
out = ball[:]
for v, b in enumerate(ball):
if b:
for u in nbr[v]:
out[u] |= b
return out
def main():
data = sys.stdin.buffer.read().split()
n, m, s, p = int(data[0]), int(data[1]), int(data[2]), int(data[3])
edges = [(int(data[4 + 2 * i]) - 1, int(data[5 + 2 * i]) - 1) for i in range(m)]
print(solve(n, edges, s, p))
if __name__ == "__main__":
main()
計算量: 行き先の集合は、通り抜け禁止なら警官の位置ごとに半径を $S$ 回広げて $O(N\cdot S\cdot M)$ 回のビット列 OR、通り抜け可なら 1 回だけで $O(S\cdot M)$。後退解析は 1 段ごとに変化した $c$ の未確定の $r$ を調べるので $O(\text{段数}\cdot N^2)$ 回のビット列演算(1 回は $N/64$ 語)。実測(Python 3.14、Apple M5 Pro、起動込み)で、$N=500,M=2\times10^4$ の乱択グラフ $S=3,P=0$ が 1.67 s、$P=1$ が 0.05 s、$N=500$ の道(答え 250、段数 250)が 0.19 s。
Step-by-Step 解説
1変わるのは行き先の集合だけ
Day176-Q1 の後退解析は「泥棒の手番の局面の値=行き先の値の最大」「警官の手番の局面の値=行き先の値の最小+1」を、確定した局面から逆向きに広げるものだった。泥棒の速さや通り抜けの規則は「泥棒の行き先」を変えるだけなので、$\mathrm{Mv}_c(r)$ を作り直せばそのまま使える。
2通り抜け禁止の球と通り抜け可の球
- 通り抜け禁止: $c$ を消したグラフで半径 $S$ の球。
ball[c] = 0にしてから「自分の球に隣の球を足す」を $S$ 回。$c$ の球が常に空なので、$c$ を経由する広がりが起きない - 通り抜け可: 元のグラフで半径 $S$ の球を 1 回作り、警官ごとに $c$ のビットだけ消す
通り抜け可の方が作るのは $N$ 倍速い。実測で同じグラフの $P=1$ が 0.05 s、$P=0$ が 1.67 s と差が大きいが、$P=1$ では泥棒がほぼ常に逃げ切って段数も少ないので、差のうち球の作り方の分がどれだけかは分けて測っていない。
3なぜ速さ 3 で通り抜けが効くのか
速さ 2 で警官 $c$ を通り抜けると、$r\to c\to x$ の $x$ は $c$ の隣なので、次の手で捕まる。速さ 3 なら $r\to c\to x\to y$ と、警官から距離 2 の $y$ まで行ける。入力例 2 の道 1-2-3-4 では、警官がどちらの端に寄っても泥棒は反対の端(距離 2)へ跳べるので、木でも警官が勝てない。
4後退解析の段
段 $t$ で新しく $T=t$ と確定した警官の手番 $(c,r)$ を new[c] に持つ。
done[c] |= new[c]new[c]が増えた $c$ について、まだ値の無い泥棒の手番 $(c,r)$ のうちmv[c][r] & ~done[c] == 0(行き先が全部確定)のものは値 $t$(行き先の最大は今の段の値)- 泥棒の値が $t$ になった $(c',r)$ から、$c'$ の隣と $c'$ 自身の $c$ について $(c,r)$ を $T=t+1$ の候補にする
値は段の順に確定するので、警官の手番の「最初に候補になった段」が最小値になる。
5答え
最初の配置は警官が先に選び、泥棒が後から選ぶので、答えは $\min_c\max_{r\ne c}T(c,r)$。$c$ のどれも全部の $r$ が確定しなければ $-1$。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 値反復(全局面を毎回更新) | $O(\text{手数}\cdot N^2\cdot(\text{行き先}))$ | 検算用のみ | 小さいグラフの全探索として使った |
| 速さ 2 の解(Day177-Q2)をそのまま使う | 同じ | 不採用(誤り) | 1200 入力(600 グラフ × $P$)中 109 で手数が、106 で勝敗が違う(実測) |
| 通り抜けの規則を無視する(常に禁止) | 同じ | 不採用(誤り) | 1200 入力中 102 で誤り($P=0$ の入力では正しいので、誤りはすべて $P=1$ の 600 入力の中) |
| 速さ 1 の判定(解体可能性)で勝敗を決める | $O(N^3)$ | 不採用(誤り) | 1200 入力中 140 で勝敗が違う |
| 行き先を泥棒の位置ごとに BFS で毎回作る | $O(N^2\cdot M)$ | 不採用 | 球のビット列を $S$ 回の OR で作れば $O(N\cdot S\cdot M)$ 回の演算で済む |
| 行き先の集合+ビット列の後退解析(本解) | 上記 | 採用 | 最悪 1.67 s |
前提が変わったら
- 警官が 2 人以上なら局面が $N^{k+1}$ になり、$N=500$ は無理。警官数 2 なら $N\le60$ 程度に下げて同じ後退解析
- 泥棒の速さが無限(グラフの連結成分内ならどこへでも)なら、Seymour–Thomas の木幅との関係(原典未確認)の世界になり、後退解析より構造の議論が要る
- 警官も速さ 2 以上なら、警官の行き先も球にするだけで同じ枠組み
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 通り抜け禁止の球を「元の球から $c$ を消す」で作る | 終点の条件と途中の条件を混同 | $c$ を消したグラフで球を作る(入力例 1 と 2 の違い) |
| 泥棒の「留まる」を入れ忘れる | 長さ 0 の歩道を数えない | 球の初期値を自分自身にする |
| 泥棒の手番の値を「最初に確定した行き先」で決める | 警官の手番と同じ処理にしてしまう | 泥棒は最大化なので、行き先が全部確定してから |
| 答えを $\max_r\min_c$ にする | 配置の順番の読み違い | 警官が先に置くので $\min_c\max_r$ |
| 警官の「留まる」を入れ忘れる | 隣だけを候補にする | $c'$ は $c$ の隣と $c$ 自身 |
次のステップ
- 発展1: 通り抜け可・不可の両方で警官が勝つとき、手数が違う例を探す(本セッションの 6 頂点以下の全列挙と 7〜12 頂点の乱択 3000 個では 1 つも無かった。証明も反例もまだ無い)
- 発展2: 速さ 3・通り抜け禁止で警官が勝つグラフを特徴づける(Chalopin ほかの「速い泥棒」の結果を原典で確かめてから)
- 発展3: 警官 2 人・速さ 1 の後退解析を $N=60$ で書き、警官数 2 で勝てる最小のグラフを探す
検証ログ(本セッションで実行した数値)
- 値反復との一致(実測): $N=1..9$ の乱択連結グラフ 750 個 × $(S,P)\in\{1,2,3\}\times\{0,1\}$ の 4500 通りで、本解と値反復(行き先を BFS で作る素朴な実装)が全件一致
- 誤った方法(実測、$N=2..10$ の乱択 600 グラフ × $P\in\{0,1\}$、$S=3$ の 1200 入力): 速さ 2 の解 109 入力(勝敗 106)、通り抜けを無視 102 入力、解体可能性で判定 140 入力で誤り
- 全列挙(実測、本解を使用): 頂点数 4・5・6 のラベル付き連結グラフ 38・728・26704 個で、速さ 3 の警官の勝ちは通り抜け禁止 35・556・14259 個、通り抜け可 23・256・5319 個。勝敗が分かれたのは 12・300・8940 個で、4 頂点の 12 個はちょうど道 $P_4$ のラベル付け($4!/2=12$)。速さ 2 では通り抜けの有無で答えが変わるグラフは 0 個(Day177-Q2 の主張と一致)。両方で警官が勝つのに手数だけ違うグラフは 0 個(7〜12 頂点の乱択 3000 個でも 0 個)
- 速さ 2 なら警官の勝ちで速さ 3(通り抜け禁止)なら泥棒の勝ち、になるグラフは 5 頂点以下で 0 個、6 頂点で 450 個(入力例 3 はその 1 つ)
- 時間(実測、起動込み): 乱択 $N=300,M=3000$ で $S=3,P=0$ 0.17 s、$N=500,M=5000$ 0.50 s、$N=500,M=2\times10^4$ 1.67 s($S=2$ は 1.14 s)。道 $N=500$ 0.19 s(答え 250)、細長い木 $N=500$ 0.21 s(答え 126)、格子 $22\times22$ 0.14 s。$P=1$ はどれも 0.05 s 以下。メモリは最大 47 MB
- 扱っていない範囲: 原典の確認、警官が複数の場合、手数だけが違う例の有無の証明