[TJOI2018] 异或

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

Euler 前缀版本回答子树,根到点版本配合 LCA 四根差分回答路径最大异或。

OJ: luogu

题目 ID: P4592

难度:省选/NOI-

标签:可持久化TrieEuler序LCA树上路径python

日期: 2026-07-16 19:57

题意

树上点有权值,询问某个子树或两点路径中,点权与给定 z 的最大异或值。

思路

DFS 序中一个子树是连续区间 [entry[x], exit[x]]。按 Euler 顺序建立 Trie 前缀版本,就能用两个版本的计数差回答子树。

路径查询另为每个节点建立“根到该点所有点权”的版本 path_root[u]。设 a=lca(x,y),路径点权多重集等于:

text
root[x] + root[y] - root[a] - root[parent[a]]

每个二进制位同时查看四个版本的目标儿子;正版本计数之和大于负版本时即可走入。LCA 用普通倍增求出。

两类版本的节点结构完全相同,因此共用三个节点数组。

Python 知识

  • 显式进入/离开事件栈同时得到 entryexit 和 DFS 顺序,避免递归爆栈。
  • map(previous.__getitem__, previous) 构造 LCA 倍增下一层。
  • array("i") 保存父亲、深度、版本根和 Trie 节点,控制六百万级节点的内存。
  • 查询先读成整数列表,再按操作类型解包不同参数。

代码

python
import sys
from array import array


MAX_BIT = 29
input = sys.stdin.buffer.readline
n, query_count = map(int, input().split())
values = array("I", map(int, input().split()))
graph = [[] for _ in range(n)]
for _ in range(n - 1):
    u, v = map(int, input().split())
    u -= 1
    v -= 1
    graph[u].append(v)
    graph[v].append(u)

parent = array("i", [-1]) * n
depth = array("i", [0]) * n
entry = array("i", [0]) * n
exit = array("i", [0]) * n
order = array("i")
stack = [(0, -1, False)]

while stack:
    node, previous, leaving = stack.pop()
    if leaving:
        exit[node] = len(order) - 1
        continue
    parent[node] = previous
    depth[node] = depth[previous] + 1 if previous >= 0 else 0
    entry[node] = len(order)
    order.append(node)
    stack.append((node, previous, True))
    stack.extend((neighbor, node, False)
                 for neighbor in reversed(graph[node]) if neighbor != previous)
del graph, stack

jump = [array("i", (previous if previous >= 0 else 0 for previous in parent))]
for _ in range(1, n.bit_length()):
    previous = jump[-1]
    jump.append(array("i", map(previous.__getitem__, previous)))


def lca(first, second):
    if depth[first] < depth[second]:
        first, second = second, first
    difference = depth[first] - depth[second]
    level = 0
    while difference:
        if difference & 1:
            first = jump[level][first]
        difference >>= 1
        level += 1
    if first == second:
        return first
    for level in range(len(jump) - 1, -1, -1):
        if jump[level][first] != jump[level][second]:
            first, second = jump[level][first], jump[level][second]
    return jump[0][first]


child_zero = array("i", [0])
child_one = array("i", [0])
count = array("i", [0])


def clone(node):
    child_zero.append(child_zero[node])
    child_one.append(child_one[node])
    count.append(count[node])
    return len(count) - 1


def insert(previous_root, value):
    root = clone(previous_root)
    count[root] += 1
    previous, current = previous_root, root
    for bit in range(MAX_BIT, -1, -1):
        if value >> bit & 1:
            child = clone(child_one[previous])
            child_one[current] = child
            previous = child_one[previous]
        else:
            child = clone(child_zero[previous])
            child_zero[current] = child
            previous = child_zero[previous]
        current = child
        count[current] += 1
    return root


path_root = array("i", [0]) * n
for node in order:
    previous_root = path_root[parent[node]] if parent[node] >= 0 else 0
    path_root[node] = insert(previous_root, values[node])

euler_root = array("i", [0])
for node in order:
    euler_root.append(insert(euler_root[-1], values[node]))


def range_maximum(older_root, newer_root, value):
    answer = 0
    for bit in range(MAX_BIT, -1, -1):
        if value >> bit & 1:
            wanted_old, wanted_new = child_zero[older_root], child_zero[newer_root]
            other_old, other_new = child_one[older_root], child_one[newer_root]
        else:
            wanted_old, wanted_new = child_one[older_root], child_one[newer_root]
            other_old, other_new = child_zero[older_root], child_zero[newer_root]
        if count[wanted_new] > count[wanted_old]:
            answer |= 1 << bit
            older_root, newer_root = wanted_old, wanted_new
        else:
            older_root, newer_root = other_old, other_new
    return answer


def path_maximum(first_root, second_root, lca_root, parent_root, value):
    answer = 0
    for bit in range(MAX_BIT, -1, -1):
        children = child_zero if value >> bit & 1 else child_one
        other = child_one if value >> bit & 1 else child_zero
        first_child, second_child = children[first_root], children[second_root]
        lca_child, parent_child = children[lca_root], children[parent_root]
        if count[first_child] + count[second_child] > count[lca_child] + count[parent_child]:
            answer |= 1 << bit
            first_root, second_root = first_child, second_child
            lca_root, parent_root = lca_child, parent_child
        else:
            first_root, second_root = other[first_root], other[second_root]
            lca_root, parent_root = other[lca_root], other[parent_root]
    return answer


answers = []
for _ in range(query_count):
    query = list(map(int, input().split()))
    if query[0] == 1:
        node, value = query[1] - 1, query[2]
        answers.append(str(range_maximum(euler_root[entry[node]],
                                         euler_root[exit[node] + 1], value)))
    else:
        first, second, value = query[1] - 1, query[2] - 1, query[3]
        ancestor = lca(first, second)
        ancestor_parent = parent[ancestor]
        answers.append(str(path_maximum(path_root[first], path_root[second],
                                        path_root[ancestor],
                                        path_root[ancestor_parent] if ancestor_parent >= 0 else 0,
                                        value)))

print("\n".join(answers))

复杂度

预处理 O(nlogn+30n)O(n\log n+30n),每次子树询问 O(30)O(30),路径询问 O(logn+30)O(\log n+30);空间 O(nlogn+30n)O(n\log n+30n)

总结

子树靠 Euler 区间差,路径靠根版本四项容斥;两种树上集合查询最终都落到同一个“版本节点计数差”。