概要
根付き木 \(T\) のある頂点 \(u, v\) について、共通の祖先であり、根頂点から最も遠い位置にあるLCAの頂点を求める。
平方分割を使ったアルゴリズムでは、セグメント木と同様にEuler tour techniqueを用いてLCAを計算する。
計算量
-
前計算: \(O(N)\)
-
クエリ処理: \(O(\sqrt{N})\)
実装
# N: 頂点数
# G[v]: 頂点vの子頂点 (親頂点は含まない)
N = ...
G = [[...] for i in range(N)]
# Euler Tour Technique
S = []
F = [0]*N
depth = [0]*N
def dfs(v, d):
F[v] = len(S)
depth[v] = d
S.append(v)
for w in G[v]:
dfs(w, d+1)
S.append(v)
dfs(0, 0)
# ==== 平方分割の構築
INF = (N, None)
# sN: 1つのバケットに含む列の長さ (同時に分割するバケットの数)
sN = int(len(S)**0.5)+1
N0 = sN**2
S0 = [INF]*N0
for i, v in enumerate(S):
S0[i] = (depth[v], v)
# sN個のバケットのdepth最小値を計算
GR = [min(S0[i:i+sN]) for i in range(0, N0, sN)]
# 各バケット内の、左端右端を含む区間のdepth最小値を計算
LV = [None]*N0; RV = [None]*N0
for i in range(0, N0, sN):
# RV[i] = min(S0[(i//sN)*sN, i+1])
RV[i] = r = S0[i]
for j in range(i+1, i+sN):
RV[j] = r = min(r, S0[j])
# LV[i] = min(S0[i, (i//sN+1)*sN])
LV[i+sN-1] = r = S0[i+sN-1]
for j in range(i+sN-2, i-1, -1):
LV[j] = r = min(r, S0[j])
# 平方分割を使ったLCAの計算
def query(u, v):
fu = F[u]; fv = F[v]
if fu > fv:
fu, fv = fv, fu
gu = fu // sN
gv = fv // sN
if gu == gv:
# 同じバケットのS[fu:fv+1]のdepth最小値の頂点w
return min(S0[fu:fv+1])[1]
if gu+1 < gv:
# fu, fvを含まないバケットgu+1, ..., gv-1のdepth最小値の頂点w
return min(LV[fu], min(GR[gu+1:gv]), RV[fv])[1]
# fu, fvを含まないバケットが存在しない(gu+1 == gv)場合のdepth最小値の頂点w
return min(LV[fu], RV[fv])[1]
Verified
-
AOJ: "GRL_5_C: Tree - Lowest Common Ancestor": source (Python3, 2.08sec)