[SDOI2014] 旅行

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

HLD 拆路径,按宗教维护动态线段树,回答同宗教城市的评级和与最大值。

OJ: luogu

题目 ID: P3313

难度:省选/提高

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

日期: 2026-07-17 02:00

题意

支持修改城市宗教、修改评级,以及查询路径上指定宗教城市的评级和或最大值。

思路

HLD 把路径拆成 DFS 序区间。为每种宗教维护一棵动态线段树,只有该宗教当前出现的位置建立节点;宗教修改就是旧树叶子清零、新树叶子写入,评级修改只更新当前宗教的一片叶子。路径上逐段查询并合并和与最大值。

Python 知识

  • array("i")/array("q") 作为动态线段树节点池,避免为每个节点创建对象。
  • roots[color] 保存每种宗教的根,空节点 0 共享给所有宗教。
  • 点更新沿 DFS 序路径迭代回溯,区间查询返回 (sum, max) 元组。

代码

python
import sys
from array import array


sys.setrecursionlimit(1_000_000)
input = sys.stdin.buffer.readline
n, operations = map(int, input().split())
weight = [0] * (n + 1)
color = [0] * (n + 1)
for node in range(1, n + 1):
    weight[node], color[node] = 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)
subtree[0] = 0
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)
timer = 0
chains = [(1, 1)]
while chains:
    node, chain_top = chains.pop()
    while node:
        top[node] = chain_top
        timer += 1
        dfn[node] = timer
        for neighbor in graph[node]:
            if neighbor != parent[node] and neighbor != heavy[node]:
                chains.append((neighbor, neighbor))
        node = heavy[node]

# Node 0 is the shared empty node. Each religion owns one root in the same
# dynamic segment-tree pool.
left_child = array("i", [0])
right_child = array("i", [0])
node_sum = array("q", [0])
node_max = array("i", [0])
roots = array("i", [0]) * 100_001


def new_node():
    left_child.append(0)
    right_child.append(0)
    node_sum.append(0)
    node_max.append(0)
    return len(left_child) - 1


def point_set(root, position, value):
    if root == 0:
        root = new_node()
    node = root
    left, right = 1, n
    path = []
    while left < right:
        path.append(node)
        middle = (left + right) // 2
        if position <= middle:
            if left_child[node] == 0:
                left_child[node] = new_node()
            node = left_child[node]
            right = middle
        else:
            if right_child[node] == 0:
                right_child[node] = new_node()
            node = right_child[node]
            left = middle + 1
    node_sum[node] = value
    node_max[node] = value
    for node in reversed(path):
        node_sum[node] = node_sum[left_child[node]] + node_sum[right_child[node]]
        node_max[node] = max(node_max[left_child[node]], node_max[right_child[node]])
    return root


def range_query(node, left, right, query_left, query_right):
    if node == 0:
        return 0, 0
    if query_left <= left and right <= query_right:
        return node_sum[node], node_max[node]
    middle = (left + right) // 2
    total = maximum = 0
    if query_left <= middle:
        total, maximum = range_query(left_child[node], left, middle, query_left, query_right)
    if middle < query_right:
        right_sum, right_max = range_query(right_child[node], middle + 1, right, query_left, query_right)
        total += right_sum
        maximum = max(maximum, right_max)
    return total, maximum


for node in range(1, n + 1):
    roots[color[node]] = point_set(roots[color[node]], dfn[node], weight[node])


def path_query(x, y, religion):
    total = maximum = 0
    root = roots[religion]
    while top[x] != top[y]:
        if depth[top[x]] < depth[top[y]]:
            x, y = y, x
        part_sum, part_max = range_query(root, 1, n, dfn[top[x]], dfn[x])
        total += part_sum
        maximum = max(maximum, part_max)
        x = parent[top[x]]
    if depth[x] > depth[y]:
        x, y = y, x
    part_sum, part_max = range_query(root, 1, n, dfn[x], dfn[y])
    return total + part_sum, max(maximum, part_max)


answers = []
for _ in range(operations):
    operation, x, value = input().split()
    x, value = int(x), int(value)
    if operation == b'CC':
        roots[color[x]] = point_set(roots[color[x]], dfn[x], 0)
        color[x] = value
        roots[color[x]] = point_set(roots[color[x]], dfn[x], weight[x])
    elif operation == b'CW':
        weight[x] = value
        roots[color[x]] = point_set(roots[color[x]], dfn[x], value)
    else:
        total, maximum = path_query(x, value, color[x])
        answers.append(str(total if operation == b'QS' else maximum))
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);动态节点数为所有实际宗教-位置组合的对数级总量。

总结

当筛选条件是动态颜色时,可以把颜色放到外层根数组,把位置线段树节点按需创建。