Day 071-Q4 — Level Ancestor Query(Binary Lifting + LCA統合)

2026-06-24 赤色 Master / Phase 8+ ★★★★★★★★★ LAQ・Binary Lifting・LCA

問題

$N$ 頂点の根付き木(根 = 1)が与えられる。$Q$ 個のクエリに答えよ。

  • LA v k: 頂点 $v$ の $k$ 個上の祖先の頂点番号(存在しない場合は -1
  • LCA u v: 頂点 $u$ と $v$ の最小共通祖先

制約

パラメータ範囲
$N$$1 \le N \le 5 \times 10^5$
$Q$$1 \le Q \le 5 \times 10^5$
$k$$0 \le k \le N$

入出力例

入力例 1

7 5
1 1 2 2 3 3
LA 5 2
LA 6 1
LCA 5 6
LA 4 3
LCA 4 7

出力例 1

1
2
2
-1
1

木の構造: 1→{2,3}, 2→{4,5}, 3→{6,7}。LA(5,2)=1(5の2個上は1)。LA(4,3)=-1(4の深さは2なので3個上は存在しない)。

概念図: Binary Lifting テーブル

Binary Lifting: up[k][v] = v の 2^k 個上の祖先 up[k][v] = up[k-1][up[k-1][v]] で前処理 O(N log N), クエリ O(log N) 例: 7頂点の木 1 d=0 2 d=1 3 d=1 4 d=2 5 d=2 6 d=2 7 d=2 LA(5,1)=2 LA(5,2)=1 up テーブル (up[k][v] = v の 2^k 個上) k=0 (2^0=1個上) k=1 (2^1=2個上) k=2 (4個上) v=1: up[0][1]=1* up[1][1]=1* up[2][1]=1* v=2: up[0][2]=1 up[1][2]=1* up[2][2]=1* v=3: up[0][3]=1 up[1][3]=1* up[2][3]=1* v=4: up[0][4]=2 up[1][4]=1 up[2][4]=1* v=5: up[0][5]=2 up[1][5]=1 up[2][5]=1* v=6: up[0][6]=3 up[1][6]=1 up[2][6]=1* v=7: up[0][7]=3 up[1][7]=1 up[2][7]=1* *は番哨(根の親=根自身) LA(5, 2=10₂): k=1が立っている → up[1][5] = 1 ✓ LA(5, 3=11₂): k=0→up[0][5]=2, k=1→up[1][2]=1 (depth=2 < 3 → -1)

ヒント(段階的開示)

ヒント1: 方向性
LA クエリは Binary Lifting(倍増法)で $O(\log N)$ で処理可能。up[v][k] = 頂点 $v$ の $2^k$ 個上の祖先を前処理で構築。LCA も同様の Binary Lifting で $O(\log N)$。両方を統合して実装する。
ヒント2: アプローチ

前処理: BFS で depth と up[0] を設定。その後 up[k][v] = up[k-1][up[k-1][v]] で構築。

LA クエリ: $k = \sum_i b_i 2^i$ の各ビット $b_i$ が立っている桁分ジャンプ。

LCA クエリ: 深い方を LA で引き上げ → 同じ頂点なら返す → 異なる場合は大きい桁から試してジャンプ。

ヒント3: コード骨格
LOG = 20
up = [[0] * (N + 1) for _ in range(LOG)]
up[0][root] = root  # 番哨

for k in range(1, LOG):
    for v in range(1, N + 1):
        up[k][v] = up[k-1][up[k-1][v]]

def la(v, k):
    if k < 0 or k > depth[v]: return -1
    for i in range(LOG):
        if (k >> i) & 1:
            v = up[i][v]
    return v

def lca(u, v):
    if depth[u] < depth[v]: u, v = v, u
    u = la(u, depth[u] - depth[v])
    if u == v: return u
    for k in range(LOG-1, -1, -1):
        if up[k][u] != up[k][v]:
            u = up[k][u]; v = up[k][v]
    return up[0][u]

模範解答 (Python)

import sys
from collections import deque
input = sys.stdin.readline

def solve():
    N, Q = map(int, input().split())
    parent = [0] * (N + 1)
    children = [[] for _ in range(N + 1)]

    if N > 1:
        p_list = list(map(int, input().split()))
        for i, p in enumerate(p_list, start=2):
            parent[i] = p
            children[p].append(i)
    else:
        input()

    LOG = 20
    up = [[0] * (N + 1) for _ in range(LOG)]
    depth = [0] * (N + 1)

    root = 1
    up[0][root] = root  # 番哨
    bfs = deque([root])
    visited = [False] * (N + 1)
    visited[root] = True

    while bfs:
        v = bfs.popleft()
        for c in children[v]:
            if not visited[c]:
                visited[c] = True
                depth[c] = depth[v] + 1
                up[0][c] = v
                bfs.append(c)

    for k in range(1, LOG):
        for v in range(1, N + 1):
            up[k][v] = up[k-1][up[k-1][v]]

    def la(v, k):
        if k < 0 or k > depth[v]:
            return -1
        for i in range(LOG):
            if (k >> i) & 1:
                v = up[i][v]
        return v

    def lca(u, v):
        if depth[u] < depth[v]:
            u, v = v, u
        diff = depth[u] - depth[v]
        u = la(u, diff)
        if u == v:
            return u
        for k in range(LOG - 1, -1, -1):
            if up[k][u] != up[k][v]:
                u = up[k][u]
                v = up[k][v]
        return up[0][u]

    out = []
    for _ in range(Q):
        query = input().split()
        if query[0] == 'LA':
            v, k = int(query[1]), int(query[2])
            out.append(str(la(v, k)))
        else:
            u, v = int(query[1]), int(query[2])
            out.append(str(lca(u, v)))

    print('\n'.join(out))

solve()

Step-by-Step 解説

Step 1: Binary Lifting の仕組み

up[k][v] = 頂点 $v$ の $2^k$ 個上の祖先。再帰: up[k][v] = up[k-1][up[k-1][v]]

前処理 $O(N \log N)$、各クエリ $O(\log N)$。

Step 2: LA クエリ

$k$ を2進数で表し、ビットが立っている桁分ジャンプを繰り返す。例: $k = 5 = 101_2$ → $k=0$ のビット(1個上)→ $k=2$ のビット(4個上)の順にジャンプ。$k > \text{depth}(v)$ なら -1。

Step 3: LCA クエリ

  1. 深い方を LA で同じ深さまで引き上げる
  2. 同じ頂点なら LCA として返す
  3. 大きい桁から試し、上が異なれば両方ジャンプ
  4. 最後に一段上がれば LCA

Step 4: 番哨(Sentinel)

根の親を根自身に設定(up[0][root] = root)することで、「存在しない祖先」へのアクセスが根に収束し、境界処理が簡潔になる。ただし depth チェックは別途必要。

Step 5: 計算量分析

処理計算量
前処理(BFS + テーブル構築)$O(N \log N)$
LA クエリ$O(\log N)$
LCA クエリ$O(\log N)$
合計$O((N + Q) \log N)$

よくあるミス

ミス原因正しい書き方
LA(v, k) で k > depth[v] の扱い-1 を返す必要if k > depth[v]: return -1
根の祖先を 0(存在しない頂点)に設定LCA 計算でバグる根の親を根自身に設定
LOG 不足$N \le 5 \times 10^5 \Rightarrow \log_2 N \approx 19$LOG = 20
LCA で深さを揃えた後の判定忘れu == v のとき早期 return が必要if u == v: return u

次のステップ

  • 発展問題: $O(N)$ 前処理 $O(1)$ クエリの LA アルゴリズム(Ladder Decomposition)
  • LCA を使ったパスクエリ(HLD との統合)
  • 動的木(頂点追加)上の LA クエリ(Link-Cut Tree)
  • Level Ancestor と部分木クエリの組み合わせ問題

自己評価