树上问题
参考资料
知识
lca 问题
dfs 序
复杂度 \(O(n\log n)-O(1)\),ST 表按照 dfn 维护区间最小深度值的位置。注意查询 \([l,r]\) \(lca\) 的时候,取得是 \([l+1,r]\) 的最小深度值,再返回 \(fa\) 即可。
点分治
【学习笔记】点分治
【学习笔记】点分治 & 点分树
【9】点分治 & 点分树学习笔记
DSU on tree(静态链分治)
DSU on Tree 即树上启发式合并,重点不在“合并”,而在利用树链剖分的性质对子树问题进行复杂度正确的分治。
面对多组询问但不带修改的树上题目,可以使用 DSU on Tree 解决。
算法流程
- 递归处理轻儿子的答案
- 递归处理重儿子的答案
- 重新遍历轻儿子子树,计算当前子树的答案
- 如果当前节点是轻儿子,重新遍历整棵子树来清除答案
发现一个节点被再次遍历当且仅当其所在的子树根为轻儿子,在这之后其所在子树大小至少扩大 2 倍,因此每个节点最多被遍历 \(O(\log n)\) 次,那么总遍历次数是 \(O(n \log n)\),若单个节点计算答案复杂度 \(O(k)\),总复杂度 \(O(kn \log n)\)。
code
void dfs1(){
// 求出重儿子
}
void add(){
// 加入一个节点的贡献
}
void del(){
// 删去一个节点的贡献
}
void insert(int u){
// 加入整棵子树的贡献
add(u);
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[u]) continue;
insert(v);
}
}
void erase(int u){
// 删去整棵子树的贡献
del(u);
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[u]) continue;
erase(v);
}
}
void dfs2(int u){
// 递归处理轻儿子答案
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v);
}
// 递归处理重儿子答案
if(son[u]) dfs2(son[u]);
// 重新遍历轻儿子子树,计算当前子树的答案
add(u);
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
insert(v);
}
// 如果当前节点是轻儿子,重新遍历整棵子树,清除答案
if(fa[u]&&son[fa[u]]!=u) erase(u);
}
结论
-
重链剖分后,任意一个节点到根路径上轻儿子数量级是 \(O(\log n)\)。因为每一个轻儿子向上合并 \(siz\) 都会 \(\times 2\)。
-
以下的 \(siz_i\) 都是以 \(i\) 为根的子树大小。树的拓扑序个数是 \(n!\over \prod siz_i\),证明考虑每个以 \(u\) 为根的子树 \(u\) 的编号最小的概率。森林的拓扑序个数也是 \(n!\over \prod siz_i\),证明考虑所有树根连向一个虚点,通过一些简单计算便可得到。
trick
题目
CF1225F Tree Factory
题目大意
给定一棵目标树 \(T\),初始有一棵树 \(S\) 为一条链,你可以任意编号 \([1,n]\) 排列。每次选择一个点 \(u\),将 \(fa_u := fa(fa_u)\),求将 \(S\) 变为 \(T\) 的最小步数。并构造 \(S\) 和操作方案。
\(n \le 10^5\)
解题思路
正难则反,想想怎么将一棵树变成一条链,先考虑答案下界:\(siz-maxdep\)。这启发我们每次都是将长链往旁边移。长链剖分后,每次其实都是对一条长链,然后将它迁移到另外的短儿子那里,然后再在短儿子内合并,贪心的去取。
手动模拟一下便可发现,最后链的顺序就是长链剖分后先处理短儿子的 dfn,一个点要动当前仅当 \(dep_{fa_u}<dep_{id_{dfn_u-1}}\),简单算一算即可。
启发:
- 最优化问题可以先找出答案的上下界,进行一些观察,有时候也能二分。
P9481 [NOI2023] 贸易
题目大意
一棵 \(2^n - 1\) 个点的完全二叉树,树边都是有向边,\(i\) 向 \(i/2\) 有边权 \(a_i\) 边,还有 \(m\) 条祖先向孙子的带权边,求 \(\sum dis(u,v)\)
\(n \le 18, m \le 2^n\)
解题思路
首先,祖先向孙子这一限制肯定非常有用,这样就不会出现跨子树乱跳的情况。
因此,我们可以分类讨论一下,对于节点 \(u\),其他节点的贡献分为两类,一类是 \(u\) 子树内的点的贡献,另一类是 \(u\) 子树外的点的贡献。
对于第一类点,我们无法通过祖先向孙子的边非树边到达 \(u\),所以只需要考虑树边即可,非常容易通过树形 DP 求得,记为 \(f_u\),复杂度 \(O(n)\)。
对于第二类点,由于不会出现跨子树跳的情况,所以 \(v\) 要到 \(u\),肯定要先到 \(l=lca(u,v)\),我们考虑能否高效算出 \(dis(l,u)\)。
现在转化为算出点 \(u\) 对它的子树内节点 \(v\) 的最短路,首先可以从 \(fa_u\) 继承过来,然后跑 \(dij\)。考虑总时间复杂度,每个点只会在它的祖先被考虑到,所以会算 \(O(n)\) 次 \(O(2^n n)\) 的最短路,总时间复杂度就是 \(O(2^nn^2)\)。
启发:
- 通过对题目限制的观察,把贡献拆开来考虑。
P4775 [NOI2018] 情报中心
题目大意
有一棵 \(n\) 个点的树,每条边有边权 \(c_i\)。有 \(m\) 条路径 \((x_i, y_i)\),代价 \(v_i\)。
你要选两条路径,满足这两条路径至少有一条公共边,且至少被其中一条路径覆盖的边权总和减代价总和最大。输出最大值。
\(n \le 5 \times 10^4, m \le 10^5\)。
解题思路
令 \(w\) 为路径的花费 \(c_{(u,v)}=dis{(u,v)}-2w\),
可能的两种合法方案如图。对于两种情况,收益的两倍均为:

考虑枚举 \(t = lca(u_1, u_2)\) (即相交部分最深的点)
然后求出最大的
还要加上此时确定的 \(-2dep_{t}\)。
这可以看做将 \(v_1\) 的权值定为 \(w_{v_1} = c_{(u_1,v_1)} + dep_{u_1}\) ,而 \(v_2\) 的权值定为 \(w_{v_2} = c_{(u_2,v_2)} + dep_{u_2}\)。
选择 \(v_1, v_2\) 两者配对的贡献为 \(w_{v_1} + w_{v_2} + dis(v_1, v_2)\),这相当于是一个带权直径问题,根据直径的相关性质可以用线段树维护,下标就是给出的每条路径的选择情况(\(1\sim m\)),结合后面的,用线段树合并非常不错。
\(u_1,u_2\) 选择和 \(v_1,v_2\) 也是对称等价的,考虑哪个都一样,下面考虑 \(u_1,u_2\)。现在问题就变成了我们枚举 \(t\),考虑满足 \(u_1,u_2\) 在 \(t\) 子树内,\(v_1,v_2\) 在 \(t\) 子树外,这个可以用树上差分解决,也就是在 \(u\) 处挂上 \(w_u\),在 \(u\sim lca_{u,v}\) 的倒数第二个点(也就是非 lca 深度最小的点)减去贡献,\(v\) 也是同理,所以相当于将一条路径拆成两条,剩下就是 \(dfs\) 跑一遍钦定 \(t=u\) 计算答案然后父亲与儿子线段树合并了。
如果是用 \(O(1)\) lca 的话,时间复杂是 \(O((n+m)\log m)\)。
实现上有些细节:
-
线段树下标是 \(1\sim m\) 的,即边的选取情况,维护的是 不要求一定跨过 \(rt\) 的最长带权路径的两端点,如果要求一定跨过可以在合并两个集合时特别要求一下,也就是枚举的两个端点一定要在 \(rt\) 两侧,返回这个,但是维护的依旧是不要求一定跨过的。
-
要注意,我们这里没有考虑当 \(u_1\) 是 \(u_2\) 的祖先的情况,我们可以在加入的时候,统计一下。
这道题是找 AI 帮忙调代码的,有空的时候自己要补完。
[]
题目大意
给定一颗 \(n\) 个节点有根树,点 \(i\) 权值为 \(a_i\)。
在这个树上支持一种询问:
给定节点 \(u\) 和参数 \(x\), 假如所有节点点权加 \(x\),求对于所有完全在 \(u\) 子树内并包含 \(u\) 的连通点集,权值之和最大可能值。
\(1 \le n,m \le 10^6, |a_i|,|x| \le 10^8\)。
题目大意
给定一棵 \(n\) 个节点的树,树根为 1,每条边有边权 \(d_i\)。
定义 \(dep(x)\) 表示一个点到根简单路径上边权的和, \(lca(x, y)\) 表示 \(x, y\) 节点在树上的最近公共祖先。
共 \(m\) 组询问,每次询问给出 \(l, r\),求对于所有点编号的二元组 \((i, j)\) 满足 \(l \le i, j \le r\) ,有多少种不同的 \(dep(lca(i, j))\)。
\(n \le 10^5, m \le 5 \times 10^5\)。
题目大意
给定一棵有边权的无根树,需要回答一些询问。
定义 \(\text{dist}(i,j)\) 代表树上点 \(i\) 和点 \(j\) 之间的距离。
对于每一组询问,会给出 \(l,r\),你需要输出 \(\min(\text{dist}(i,j))\),其中 \(l \le i < j \le r\)。
\(n \le 2 \times 10^5, q \le 10^6\)。
题目大意
给出一颗 \(n\) 个节点的树,和一个长度为 \(m\) 的序列 \(a\),\(q\) 次询问包含 \(a_{l \dots r}\) 中所有节点的最小联通块大小。
\(n, m, q \le 10^5\)。
[]
题目大意
给你一棵带点权 \(v_i\) 的树,树以 1 为根。初始点权 \(v_i\) 均为 0。
定义 \(\text{dis}(x,y)\) 为树上 \(x,y\) 之间的距离,即 \(x \to y\) 的简单路径上的边数。
设 \(\text{subtree}(x)\) 为树上以 \(x\) 为根的子树,定义
现在给出 \(m\) 次操作,每次操作中给出 \(x,w,y\),先令 \(v_x \leftarrow v_x + w\),然后
求 \(\sum_{i \in \text{subtree}(y)} f(i)\)。
\(n,m \le 3 \times 10^5\)。

浙公网安备 33010602011771号