[HEOI2016/TJOI2016] 树

GitHub跳转原题关系图返回列表

HLD 后在线段树维护已标记 DFS 序最大值,逐链向上找到最近祖先。

OJ: luogu

题目 ID: P4092

难度:提高

标签:重链剖分线段树祖先查询python

日期: 2026-07-17 02:00

题意

标记节点,查询某节点到根路径上最近的已标记祖先。

思路

在 DFS 序位置保存“已标记则为 dfn,否则 0”。查询从节点所在链向上,线段树求 [top[x], x] 的最大 dfn;当前链找到即为最近祖先,否则跳到父链。根初始标记。

Python 知识

  • 点标记是幂等的,叶子直接写入 dfn 后向上更新最大值。
  • 迭代线段树区间最大值避免频繁递归。
  • inverse[dfn] 把线段树位置恢复为节点编号。

代码

python
import sys


input = sys.stdin.buffer.readline
n, operations = map(int, input().split())
children = [[] for _ in range(n + 1)]
for _ in range(n - 1):
    parent, child = map(int, input().split())
    children[parent].append(child)

parent = [0] * (n + 1)
depth = [0] * (n + 1)
order = [1]
for node in order:
    for child in children[node]:
        parent[child] = node
        depth[child] = depth[node] + 1
        order.append(child)
subtree = [1] * (n + 1)
heavy = [0] * (n + 1)
for node in reversed(order[1:]):
    subtree[parent[node]] += subtree[node]
    if subtree[node] > subtree[heavy[parent[node]]]:
        heavy[parent[node]] = node
top = [0] * (n + 1)
dfn = [0] * (n + 1)
inverse = [0] * (n + 1)
timer = 0
chains = [(1, 1)]
while chains:
    node, chain_top = chains.pop()
    while node:
        top[node] = chain_top
        timer += 1
        dfn[node] = timer
        inverse[timer] = node
        for child in children[node]:
            if child != heavy[node]:
                chains.append((child, child))
        node = heavy[node]

size = 1
while size < n:
    size <<= 1
segment = [0] * (2 * size)


def mark(node):
    position = size + dfn[node] - 1
    segment[position] = dfn[node]
    position //= 2
    while position:
        segment[position] = max(segment[position * 2], segment[position * 2 + 1])
        position //= 2


def range_max(left, right):
    left, right = left - 1 + size, right + size
    answer = 0
    while left < right:
        if left & 1:
            answer = max(answer, segment[left])
            left += 1
        if right & 1:
            right -= 1
            answer = max(answer, segment[right])
        left //= 2
        right //= 2
    return answer


mark(1)
answers = []
for _ in range(operations):
    operation, node = input().split()
    node = int(node)
    if operation == b'C':
        mark(node)
        continue
    while True:
        result = range_max(dfn[top[node]], dfn[node])
        if result:
            answers.append(str(inverse[result]))
            break
        node = parent[top[node]]
print("\n".join(answers))

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-17 01:04
 * update_at: 2026-07-17 01:04
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

每次查询 O(log^2 n),每次标记 O(log n),空间 O(n)

总结

“最近祖先”在同一重链上就是 DFS 序最大的已标记位置。