树上问题杂题

CF869D The Overdosing Ubiquity

虚树。注意到对于一条非树边 \((u,v)\),起点一开始走到 \(u,v\) 间路径的哪个位置是重要的,因此把路径上的 \(\log\) 个点都拿出来建虚树,每个点的权值是其对应的所有非关键点个数,于是枚举每个起点,DFS 求出以它为起点的路径数量即可。由于非树边的分叉很少,所以直接暴力 DFS 是可行的。

P8123 [BalticOI 2021 Day1] Inside information

将边的加入时间作为边权,询问变为如下两个形式:

  • 查询 \(a\to d\) 的路径是否满足边权单调递增且最大权值不大于 \(x\)
  • 查询有多少个点满足从 \(a\) 出发到它的路径边权单调递增且最大权值不大于 \(x\)

考虑离线询问点分治。对于一个分治中心,考虑按照边权从大到小的顺序逐个考虑每个子树,先 DFS 找到所有满足根过去边权递减的点,遍历以它为起点的所有询问;再 DFS 找到所有满足根过去边权递增的点,将单点连带父亲边权标记合法;并将其加入树状数组便于第二种查询。注意特判起点终点为根的情况。

总复杂度 \(O(n\log^2 n)\)

P11241 「KTSC 2024 R2」病毒

考虑一个建图方式:建立 \(n\) 个点表示病毒的中转点,对每个人的点 \(i\),找到所有距离 \(p_i\) 小于等于 \(d_i\) 的树上点,它们的中转点向 \(i\)\(0\) 的边,\(i\) 向它们的中转点连 \(c_i\) 的边。直接跑 dij 可以做到 \(O(n^2\log n)\)

考虑优化“\(i\) 这个人向所有距离它小于等于 \(d_i\) 的中转点连权值为 \(v\) 的正向或反向边”。考虑点分治优化,一个深度为 \(dep_i\) 的点能向所有深度小于等于 \(d_i-dep_i\) 的点连边。下面以正向为例:考虑建立 \(dep+1\) 个虚点,每个虚点向该深度下的实际中转点连 \(v\) 的边;虚点之间由深度 \(x\to x-1\)\(0\) 边;考察每个人对应的点,向 \(d_i-dep_i\) 对应的虚点连 \(0\) 边。反向边只需要将上述所有边反向,且去掉虚点连出来的边的权值即可。

这样,点分治过程中每个连通块的点、边数为 \(O(size)\),总数为 \(O(n\log n)\) 级别。直接跑 dij 可以做到 \(O(n\log^2 n)\),已经可以通过了。

还有一些优化。对于每个中转点,我们再拆成入点和出点,这样只有 \(O(n)\) 条出入点之间的边有权值。在 dij 的过程中,我们借用 01BFS 的思想,对于有权值的边,我们扔进优先队列;对于 \(0\) 权边,我们使用普通队列,直接维护。每次弹出时,优先弹出普通堆的点即可。这样只有 \(O(n)\) 条边会进堆,复杂度做到 \(O(n\log n)\)

P10603 BZOJ4372 烁烁的游戏

点分树板子,每个点上维护连通块内该深度受到的运算、和于父亲处在该点方向的子树上每个深度受到的运算,使用动态开点线段树/vector 维护的树状数组(如果使用 vector,需要开到 \(2\times maxdep\)),是一个区间加。查询时,需要利用两个信息容斥,每个点先加上第一个部分的答案,再减去第二个部分的答案(因为这表明它会在父亲处被删除)。预处理点分树上每个点到某祖先的距离,可以通过建树时一遍 dfs 实现。

注意点分树上没有任何距离相关的和差单调性等性质。

未知来源题

题意:给一棵有点权的树,问有多少个点对 \((i,j)\) 使得两个点的点权异或小于等于路径异或和。\(n\le 2\times 10^5\)

点分治,求出每个点到分治中心 \(r\) 的路径异或和 \(d_x\)。则要统计的点对 \((x,y)\) 满足 \(a_x\oplus a_y\le d_x\oplus d_y\oplus a_r\)。考虑拆位,在首个满足小于的位上统计答案,则更高的位上有上式取等,也即 \(a_x\oplus a_y\oplus d_x\oplus d_y=a_r\),不妨将 \(a_x\oplus d_x\) 插入 Trie,在一个点上分别维护子树内 \(a_x\) 该位为 \(0/1\) 的点数。

考虑若要求下一位相等,则当 \(a_r=1\) 时两者不同,否则两者相同,枚举 \(x\) 该位的取值,分别下去继续计算;若要求下一位不同,则要计算原式满足小于的数的数量。此时显然需要 \(a_x=a_y=0/1\),且两种取值在任意情况下均成立,直接计数即可。

于是在合并一个新子树的时候,先在两个 Trie 上跑上述统计答案,再将新子树的点依次插入老子树内,一个点最多会被执行 2 次插入操作,遍历统计答案时最多将新 Trie 的每个点遍历一次,总复杂度均为 \(O(n\log V)\)。故总复杂度 \(O(n\log n\log V)\)

P5360 [SDOI2019] 世界地图

题意:给定一个 \(n\times m\) 的网格图,每行有连接 \(1,m\) 之间的额外边形成环。\(q\) 次询问,每次删除 \([l,r]\) 列中的点,求剩余点的 MST。\(n\le 100,m,q\le 10000\)

一个想法是维护前后缀列的 MST,每次询问是利用 \(1,m\) 之间的 \(O(n)\) 条边进行合并。但是加边 MST 合并的总复杂度是边数级别的,而原来的图上点数和边数同阶,故单次合并仍然是 \(O(nm\log nm)\) 的,过不去。

考虑减少一些边/点数。注意到所有前后缀状态下的 MST 只有最左和最右的两列在全过程中可能被合并到,因此我们考虑对这 \(O(n)\) 个点建立等效 MST。假设左侧 MST 为 \(G_1\),右侧 MST 为 \(G_2\),在两棵树的关键点之间有连边。则我们有结论:对于任意一侧的 MST,仅有某两个关键点路径上的最大边在未来可能被删除,其余边一定不会被删除。正确性可以考虑删掉最大边的路径一定跨越 \(G_1,G_2\),则若原路径上的次大边在下一轮可能被删除(也即是新路径最大边),那么我们从跨越位置断开,次大边应当是另一对关键点路径上的最大边。

因此考虑只保留所有的关键点,及两两关键点之间的最大边。这样的边数是 \(O(n^2)\),我们希望把他变成一棵树。考虑利用 Kruskal 重构树的结构:Kruskal 过程中是从小往大依次选边的,因此此次合并时遍历到的一条边左右两个连通块,在新增更多边的时候一定还是连通,因此每条边在两个连通块内的端点可以任选。于是我们选择连接两个连通块内任意的下一轮关键点即可。若存在一个连通块内不含下一轮关键点,则这条边此后不再会被删去(因为他不是重构树上某两个关键点的 LCA),不用保留。这样一来,所有前后缀的等效 MST 就完成了建立。

此时每一个前缀和后缀的 MST 上点边数均为 \(O(n)\)。那么对于每一次询问,只需要把前缀、后缀及 \(1,m\) 之间的所有 \(O(n)\) 条边和 \(O(n)\) 个关键点取出来跑一次 Kruskal 即可。注意这里不需要新建边。对于前后缀的处理,我们把每一列的所有新边和上一个等效 MST 的边拿出来跑 kruskal 即可。

最后注意一条边不被保留并不意味他在答案中不出现,因此需要维护不在等效 MST 中的生成树边权和。

总复杂度 \(O((q+m)n\log n)\)

P9808 [POI 2022 ~2023R1] zbo

一个 \(dis(x,y)\) 可以被拆成 \(dep_x+dep_y-2dep_{lca(x,y)}\)。对于前面两个部分,每一个 \(dep\) 都会被算到 \(2i-1\) 次,可以通过维护前缀和简单算出。然后考虑每次进入的一个 \(x\)\(\sum_{x=1}^i\sum_{y=1}^idep_{lca(x,y)}\) 的增量贡献。

这里有一个 \(dep_{lca}\) 经典的转化:枚举 \(lca\) 的每一个祖先,统计其子树内有几个 \(1\sim i-1\) 的点。不难把单点加子树询问转置成链加单点查,那么插入一个点时的增量就是从 \(1\sim x\) 的链求和。树剖即可。

P9620 歌姬

考虑找到一个集合的树根,即为 dfn 最小和最大点的 LCA,不难用 set 维护。

将自己本身(若也是关键点的话,看做一个分支)和所有儿子子树内的 size 全部取出来,我们希望变化根的位置,则只能保留一个。我们希望求出最大的分支 size,暴力想法是遍历每一个儿子,维护单点修改子树询问。

这样显然会 T 掉,不妨转置一下,变为链加单点查,这样配合上对每个儿子的取 \(\max\) 操作,就是链加、儿子求 max。这是经典问题:树剖重标号扩展。考虑对于每个点,遍历完重儿子标号之后,先给所有轻儿子提前标号,这样所有轻儿子的标号就连续了。于是链上修改正常做,儿子 \(\max\) 分轻重儿子查询即可。

注意到这种重标号方式对于子树查询,除开自己以外的子树的标号区间是连续的,但自己在是轻儿子的时候是提前标号好、不连续的;对于链上修改,链顶的标号不一定连续。

总复杂度 \(O(m\log^2 n)\)

posted @ 2025-03-03 23:27  烟山嘉鸿  阅读(48)  评论(0)    收藏  举报