2 条题解
-
0
题目描述
有 次操作,两种类型:
- 建造操作
B p
:新建一个牛棚,如果 ,则与现有牛棚 连一条双向边。 - 查询操作
Q k
:查询从牛棚 出发到它能到达的最远牛棚的距离。
保证图始终是森林(树或几棵不相连的树),动态加点和加边,在线查询。
核心性质
在树中有一个重要性质:
对于任意节点 ,距离它最远的点一定是树的某一条直径的端点之一。
因此,如果我们知道一棵树的直径的两个端点 ,那么对于树中任意点 ,
$$\text{maxDistance}(u) = \max(\text{dist}(u, a), \text{dist}(u, b)) $$其中 是树上两点间的距离。
思路设计
我们需要支持:
- 动态加边:每次加入一个点(可能连接一个旧点)。
- 维护直径:每个连通块(树)的直径信息。
- 查询距离:快速查询任意两点距离。
1. 数据结构
- 并查集:维护连通块,每个集合的根节点记录该树的直径端点 和直径长度 。
- LCA 求距离:由于树是动态生长的,可以维护每个节点的深度 和父节点 (倍增数组),支持 求两点距离。
2. 合并时的直径更新
当新点 与旧点 相连(或两棵树通过新边合并)时,新直径的候选只有三种可能:
- 原树 的直径
- 新点 到 的路径
- 新点 到 的路径
因为 是叶子节点,所以新直径必定是:
- 原直径
- 到 的路径
- 到 的路径
中最长的一条。
更一般地(如果合并两棵旧树),新直径候选来自:
- 树 的直径
- 树 的直径
- 的直径端点与 的直径端点之间的路径
3. 算法步骤
- 初始化:第一个牛棚 ,直径端点 ,长度 。
- 对于
B p
:- 新建节点 ,若 ,连接 与 。
- 更新 的深度和父节点信息。
- 找到 所在树的直径端点 。
- 计算 ,。
- 新直径长度 ,对应端点更新。
- 对于
Q k
:- 找到 所在树的直径端点 。
- 输出 。
距离计算
使用倍增 LCA:
- 预处理 表示 的 级祖先。
- $\text{dist}(u,v) = depth[u] + depth[v] - 2 \times depth[\text{LCA}(u,v)]$。
每次加边时更新新点的深度和父节点信息。
复杂度分析
- 每次加边/查询:(LCA 查询)
- 总复杂度:,满足 。
样例模拟
输入:
7 B -1 Q 1 B 1 B 2 Q 3 B 2 Q 2
过程:
B -1
:建节点 ,直径=,长度=0Q 1
:maxDistance(1) = 0B 1
:建节点 ,连 。
原直径=,长度=0
计算 dist(2,1)=1,新直径=,长度=1B 2
:建节点 ,连 。
原直径=,长度=1
dist(3,1)=2,dist(3,2)=1
新直径=,长度=2Q 3
:直径端点 ,dist(3,1)=2,dist(3,3)=0,输出 2B 2
:建节点 ,连 。
原直径=,长度=2
dist(4,1)=2,dist(4,3)=2
新直径不变,长度=2Q 2
:直径端点 ,dist(2,1)=1,dist(2,3)=1,输出 1
输出:
0 2 1
与样例一致。
总结
本题的关键点:
- 利用树的直径性质:任意点的最远点一定是直径端点。
- 动态维护直径:合并时新直径候选来自原有直径端点组合。
- LCA 求距离:使用倍增法支持快速查询。
- 并查集维护连通块:同时记录每块的直径信息。
这样就能在 时间内解决动态加边和查询问题。
- 建造操作
- 1
信息
- ID
- 3138
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 9
- 已通过
- 1
- 上传者