Qtree3

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

HLD 加线段树最小值,按根到节点顺序寻找路径上的第一个黑点。

OJ: luogu

题目 ID: P4116

难度:提高

标签:重链剖分线段树路径查询python

日期: 2026-07-17 02:00

题意

切换节点黑白颜色,查询根到给定节点路径上的第一个黑点。

思路

黑点叶子保存 dfn,白点保存无穷大。把路径拆成若干重链段后反转段列表,从根侧开始取区间最小 dfn;第一个非无穷结果就是答案。

Python 知识

  • bytearray 保存黑白状态,切换用异或。
  • 迭代线段树区间最小值支持点更新和区间查询。
  • reversed(segments) 保持查询方向与根到节点一致。

代码

python
import sys


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

parent = [0] * (n + 1)
depth = [0] * (n + 1)
order = [1]
for node in order:
    for neighbor in graph[node]:
        if neighbor != parent[node]:
            parent[neighbor] = node
            depth[neighbor] = depth[node] + 1
            order.append(neighbor)
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 neighbor in graph[node]:
            if neighbor != parent[node] and neighbor != heavy[node]:
                chains.append((neighbor, neighbor))
        node = heavy[node]

size = 1
while size < n:
    size <<= 1
infinity = n + 1
segment = [infinity] * (2 * size)
black = bytearray(n + 1)


def toggle(node):
    black[node] ^= 1
    position = size + dfn[node] - 1
    segment[position] = dfn[node] if black[node] else infinity
    position //= 2
    while position:
        segment[position] = min(segment[position * 2], segment[position * 2 + 1])
        position //= 2


def range_min(left, right):
    left, right = left - 1 + size, right + size
    answer = infinity
    while left < right:
        if left & 1:
            answer = min(answer, segment[left])
            left += 1
        if right & 1:
            right -= 1
            answer = min(answer, segment[right])
        left //= 2
        right //= 2
    return answer


answers = []
for _ in range(operations):
    operation, node = map(int, input().split())
    if operation == 0:
        toggle(node)
        continue
    segments = []
    while top[node] != top[1]:
        segments.append((dfn[top[node]], dfn[node]))
        node = parent[top[node]]
    segments.append((dfn[1], dfn[node]))
    answer = infinity
    for left, right in reversed(segments):
        answer = range_min(left, right)
        if answer != infinity:
            break
    answers.append(str(-1 if answer == infinity else inverse[answer]))
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(n)

总结

路径上“第一个”元素要同时考虑拆段顺序和段内最小位置。