問題
有向グラフ G(頂点 0〜N-1、辺数 M)と根 r=0 が与えられる。各頂点 v の「即時支配者(immediate dominator)」を求めよ。u が v を支配するとは、r から v への全パスが u を通ること。idom[r] = -1。
制約
$2 \le N \le 2 \times 10^5$
$1 \le M \le 5 \times 10^5$
根 0 から全頂点に到達可能
入出力例
入力例 1
6 7
0 1
0 2
1 3
2 3
3 4
3 5
1 5
出力例 1
-1
0
0
0
3
3
ヒント (段階的開示)
ヒント1: 方向性
Lengauer-Tarjan アルゴリズムで O((N+M) α(N+M))。
ヒント2: アプローチ
1. DFS で pre-order 番号/2. semi-dominator を計算/3. immediate dominator を導出。
ヒント3: 誘導
Union-Find 風の path compression を用いた
eval_ と link 関数で semi-dominator 計算を高速化。模範解答 (Python)
import sys
from collections import defaultdict
input = sys.stdin.readline
sys.setrecursionlimit(300000)
def solve():
N, M = map(int, input().split())
adj = defaultdict(list)
radj = defaultdict(list)
for _ in range(M):
u, v = map(int, input().split())
adj[u].append(v)
radj[v].append(u)
root = 0
order = []
parent = [-1] * N
semi = list(range(N))
idom = [-1] * N
vertex = [-1] * N
label = list(range(N))
ancestor = [-1] * N
num = [-1] * N
visited = [False] * N
stack = [(root, -1, False)]
while stack:
v, p, back = stack.pop()
if back:
pass
else:
if visited[v]:
continue
visited[v] = True
num[v] = len(order)
vertex[len(order)] = v
order.append(v)
parent[v] = p
for w in adj[v]:
if not visited[w]:
stack.append((w, v, False))
def compress(v):
if ancestor[ancestor[v]] != -1:
compress(ancestor[v])
if num[semi[label[ancestor[v]]]] < num[semi[label[v]]]:
label[v] = label[ancestor[v]]
ancestor[v] = ancestor[ancestor[v]]
def link(v, w):
ancestor[w] = v
def eval_(v):
if ancestor[v] == -1:
return v
compress(v)
return label[v]
bucket = defaultdict(list)
for i in range(len(order) - 1, 0, -1):
w = vertex[i]
for v in radj[w]:
if num[v] == -1:
continue
u = eval_(v)
if num[semi[u]] < num[semi[w]]:
semi[w] = semi[u]
bucket[vertex[num[semi[w]]]].append(w)
link(parent[w], w)
for v in bucket[parent[w]]:
u = eval_(v)
if num[semi[u]] < num[semi[v]]:
idom[v] = u
else:
idom[v] = parent[w]
bucket[parent[w]].clear()
for i in range(1, len(order)):
w = vertex[i]
if idom[w] != vertex[num[semi[w]]]:
idom[w] = idom[idom[w]]
idom[root] = -1
for v in range(N):
print(idom[v])
solve()
Step-by-Step 解説
1DFS 木の構築
根から DFS で pre-order 番号を付ける。親 parent[v] も記録。
根から DFS で pre-order 番号を付ける。親 parent[v] も記録。
2Semi-dominator
逆順に処理。path compression で効率化。
逆順に処理。path compression で効率化。
3Bucket 処理
semi が同じ頂点をまとめて idom を推定。
semi が同じ頂点をまとめて idom を推定。
4idom 修正
semi と idom が一致しなければ idom を辿って修正。
semi と idom が一致しなければ idom を辿って修正。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 到達不能頂点を含める | 未訪問の逆辺を処理 | if num[v] == -1: continue |
| 再帰深度超過 | compress が深い | 反復版か setrecursionlimit |
| bucket clear 忘れ | 二重処理 | bucket[parent[w]].clear() |
次のステップ
- Dominator Tree を使った強連結成分検出
- Virtual Tree(Auxiliary Tree)の構築