問題
左側 $N$ 頂点・右側 $M$ 頂点・$E$ 辺の二部グラフが与えられる。最大マッチング(両端点を共有しない辺集合の最大サイズ)を求めよ。
制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $N, M$ | $1 \le N, M \le 10^5$ | 左・右の頂点数 |
| $E$ | $0 \le E \le 2\times10^5$ | 辺数(重複なし) |
| $u, v$ | $1 \le u \le N,\ 1 \le v \le M$ | 左端点・右端点 |
入出力例
入力例1
3 3 4
1 1
1 2
2 1
3 3
出力例1
3
$1\text{-}2,\ 2\text{-}1,\ 3\text{-}3$ で3本マッチできる。
概念図
ヒント
ヒント1(方向性)
増加路を1本見つけるとマッチが1増える。素朴 Hungarian は $O(VE)$。複数の増加路を同時に流したい。
ヒント2(アプローチ)
Hopcroft-Karp は BFS で最短増加路の距離ラベルを作り、DFS で頂点素な増加路を一括で流す。フェーズ数 $O(\sqrt V)$、全体 $O(E\sqrt V)$。
ヒント3(ほぼ答え)
while bfs():
for u in range(1, N + 1):
if matchL[u] == 0 and dfs(u):
res += 1
模範解答
import sys
from collections import deque
def main():
data = sys.stdin.buffer.read().split()
idx = 0
n = int(data[idx]); m = int(data[idx + 1]); e = int(data[idx + 2]); idx += 3
g = [[] for _ in range(n + 1)]
for _ in range(e):
u = int(data[idx]); v = int(data[idx + 1]); idx += 2
g[u].append(v)
INF = float('inf')
matchL = [0] * (n + 1) # 左u のマッチ先(0=未マッチ)
matchR = [0] * (m + 1) # 右v のマッチ先
dist = [0] * (n + 1)
def bfs():
dq = deque()
for u in range(1, n + 1):
if matchL[u] == 0:
dist[u] = 0
dq.append(u)
else:
dist[u] = INF
found = False
while dq:
u = dq.popleft()
for v in g[u]:
w = matchR[v]
if w == 0:
found = True # 未マッチ右に到達=増加路あり
elif dist[w] == INF:
dist[w] = dist[u] + 1
dq.append(w)
return found
def dfs(u):
for v in g[u]:
w = matchR[v]
if w == 0 or (dist[w] == dist[u] + 1 and dfs(w)):
matchL[u] = v
matchR[v] = u
return True
dist[u] = INF
return False
res = 0
while bfs():
for u in range(1, n + 1):
if matchL[u] == 0 and dfs(u):
res += 1
print(res)
main()
計算量 $O(E\sqrt V)$。密グラフでも Hungarian の $O(VE)$ より高速。
Step-by-Step 解説
Step 1: マッチ配列の初期化
matchL, matchR を 0(未マッチ)で初期化。0 を「無し」の番兵に使う。
Step 2: BFS で層グラフを作る
全未マッチ左頂点を距離0で開始。マッチ辺を逆向きに辿って距離ラベルを伝播。未マッチ右に到達したら増加路が存在。
Step 3: DFS で頂点素な増加路を流す
| 条件 | 意味 |
|---|---|
dist[w] == dist[u]+1 | 最短増加路の層に沿った辺のみ使用 |
dist[u] = INF(失敗時) | 行き止まりを再探索しない → $O(\sqrt V)$ の肝 |
Step 4: フェーズ反復
BFS が増加路を見つける限り繰り返す。最短増加路長は毎フェーズ増えるためフェーズ数 $O(\sqrt V)$。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| Hungarian と同じ $O(VE)$ | BFS の層を使わない素朴DFS | dist[w]==dist[u]+1 で層制限 |
| 無限ループ | 行き止まり頂点を再探索 | 失敗時に dist[u]=INF |
| 未マッチ右の検出漏れ | matchR[v]==0 を層扱い | w==0 は即 found=True |
| 再帰上限 | 深い増加路で RecursionError | setrecursionlimit か反復DFS |
次のステップ
- 発展: König の定理で最小頂点被覆・最大独立集合を復元
- 発展: 重み付きは Hungarian(KM法)や MCMF
- 次回予告: Z-algorithm(文字列の最小周期)
自己評価
理解度: / /
自分の回答:
気づき・メモ: