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);动态节点数为所有实际宗教-位置组合的对数级总量。
总结
当筛选条件是动态颜色时,可以把颜色放到外层根数组,把位置线段树节点按需创建。