省选集训—树上技巧选讲 by zdj

树链剖分的扩展方法

树链剖分一些扩展性质:

  1. 基于重标号的深度优先搜索优化的树链剖分算法
  2. 轻重边分治处理思想
  3. 重边批量修改,轻边用了再查

NOI 轻重边

染色的方法比较神奇,我们考虑不这样处理。

其实还是相当于对于每个点以及其邻接点的转化,不妨让每个点代表其到父亲的边,并标记其是否是重边。

那么相当于给这条链上(不包括 lca) 赋值为 1,邻接点赋值为 0

考虑标号时,先标记整条重链,然后从下往上/从上往下将链上的点的邻接点也标号。

这样就满足了一条重链除了链头之外,重链标号连续,重链的某个子段的邻接点标号连续。

那么就可以线段树暴力覆盖了,注意跳轻边的时候特别处理跳过去的重儿子。

集训队互测-简单树剖练习题

重链剖分,再用一个树状数组维护树上差分便于查找某个点的点权。

这样可以直接 O(logn) 查到某条边边权。

我们考虑批量维护重边权值,而单独查询轻边权值

那么一次修改,重边权值批量变化 2k·|auav|m,差不变,可以线段树暴力打标记维护,仅有 O(logn) 条重边需要单独单点修改

那么一次查询,只会查询 O(logn) 条重边。

维护即可。

集训队互测-线段树与区间加

注意到一次操作在首次分裂成两半递归后,以左边的递归为例,每次走左儿子,那么就是给右儿子批量修改。

并且 a=len·lz,所以这是不必要的,因此我们只需要维护 vb·lz

考虑 懒标记的下传,本质上是将每个点的懒标记变为到树根的懒标记的和

所以这启发我们不维护真实的懒标记值,而是维护每个点到树根的懒标记的和

那么就相当于是子树加了(也就是往左走的给所有往左走时的右儿子子树加)

然后我们考虑怎么通过这个东西得到答案。

ans=vbi(lzilzfai)=lzi(vbivblcvbrc)

这就行了。

可以重链剖分,然后先整体递归走左儿子非重儿子的左儿子,最后整体递归走右儿子非重儿子的右儿子。

这样我们可以保证:重链除了链头编号连续,重链上所有点的左非重儿子树编号连续,右非重儿子树编号连续。

因此可以 3 个区间表达一整个子树,接下来就是区间修改的事情了。

处理一车细节后可以通过。

树上杂题

CF1585G

显然的 SG 题目。

SG(u)=mexd=0mnd{bd},其中 bd 定义为 vSubtree(u),d(u,v)=d+1sgv,注意可能这个点不存在。

根据长链剖分的相关结论,我们只需要快速计算仅有一个儿子的节点的 dp 值即可,且这本质上与在计算 son 的 dp 值后加入了一个 son 而已,可以直接 dpudpson,然后继续向后枚举计算 mex。

至于有多个儿子的时候,我们只在乎其最浅儿子深度的这些点的 dp 值,如果可以合并这些结果就好了。

这是容易的,可以直接暴力合并,根据长链剖分,复杂度正确。
然后暴力计算就好了(SG值显然不会超过 2(mnd+1)

CF772E

动态维护 [1,i1] 的虚树,现在考虑加入 i,一个很有意思的是,若询问 x,y,则回复可以让我们知道 i 是否在子树内,如果在,那么在 lca(x,y) 的哪个方向的子树也是知道的。

并且图三度化,那么可以点分治这个LCA的寻找,然后查找到 i 应该加入的位置。

细节比较多,需要注意实现。

永恒

考虑到 lcp 长度等价于 Trie 上 LCA 深度,那么如果可以计算出 Trie 树上每个节点的被计算次数就好了。

那么问题就变成了对于每个Trie树上的点 t,求所有的 x,y,满足 lca(bx,by)=t,在原树上经过 x,y 的路径个数。如果 x,y 没有祖孙关系这将是容易的。

现在考虑转化为没有祖孙关系的问题,可以使用点分治,我们只需要单独处理当前分治中心对所有点的贡献,剩下的贡献都可以当作 szx·szy

注意啊,这个 szx 是指原树上,断掉了 x 与当前点分治的那个父亲的边后的 sz 大小,需要特别说明。

那么这就容易了,将连通块内点建立虚树,然后暴力统计,至于 x,y 位于分治中心的同一个子树里的情况,可以再做一次进行容斥。

CF1930G

很唐啊,一直在想怎么在树上搞,但是事实说明计数题只需要充要条件,树形结果只是附加。

fx 为以 x 为结尾的前缀最大值个数(x 是目前序列的最后一个)

目标 fn

考虑 fxfy 的条件是什么:

  1. x<yx 不小于 rtfay 的最大值。
  2. 根到 x 的路径中,x 编号最大。
  3. xlca(x,y)x 方向上子树里最大的点

可以考虑将每个点的儿子按照子树内编号最大值从小到大排序,然后做 dfs,这样每个点的有效时间都是一个时间段,在 dfs 的时候就可以求得部分点的失效时间,这样使用树状数组维护即可。

CF1876E

考虑一个特殊情况:外向树,这种情况每条边都可以赋值为一个颜色。

考虑将原图等效为一个外向树的图。

贪心地,如果我们定根将无向边全部变为外向边,考虑这时候的内向边会起到什么作用呢?

事实上就是将其与指向它的外向边染为同一个颜色,然后就等效为一个外向边了。

那么定根后的最大颜色数事实上还是外向边条数。

则这样可以快速换根求出答案,然后做一次 dfs 模拟进行染色就好了。

一个可能的疑问是如果我们找到了一个内向边,但是栈空了怎么办。

事实上这是不可能的,因为我们将两个点作为根的方案反转,只会影响这两个点之间的路径,因此最优的根,一定到每个点的路径上外向边不少于内向边,因此栈不可能为空。

CF1930H

首先答案是不在路径上的点的点权最小值。

我们问题化为将树进行两次标号,并使用这两次标号的结果将树上除掉这条路径的图划分为不超过 5 个区间。

使用入栈序和出栈序,画图不难发现。

「CEOI2022」Drawing

可以有一个 O(n2) 的暴力解法:任意定一个在凸包上的点作为根的代表点,然后 dfs 时,做极角排序,并按照各个儿子的 sz 在极角序里面划分,前 sz1 个划给第一个子树,sz1+1sz2 划给第二个子树……

并且第 sz1 个钦定为第一个儿子的代表点,第 sz2+sz1 钦定为第二个儿子的代表点……递归即可解决。

这样做是 O(n2logn) 的,利用 nth-element 技术可以做到 O(n2)

优化划分过程,但是很遗憾的是用不了点分治(因为该点分治划分后分治中心并没有钦定)

一个神奇的想法是做链分治

递归处理,先拿出一整条重链,找到一个重链上的 mid,并将点集划分为断掉 (mid,famid) 的两部分,两部分递归处理。

这样分治下去只有几种情况:当前点集,我知道链头的代表点/我知道链头和链底的代表点。

考虑将链底与链头的两个点取凸包上相邻的两个点,这样可以保证点集的划分。

接着我们如何确定 mid 呢?也就是保证 (mid,famid) 无论 famid 怎么取都不会导致相交。

那么取 mid 保证 mid,top,down 三点的三角形内不存在其他点,并将点集划分为按照 mid 极角排序(注意到这时候需要再特别预处理一下已经确定划分到左/右的点,保证 mid 是剩下的点的凸包)后的对应大小个。就可以保证无论 mid 怎么连,(mid,famid)(mid,v),vSon(mid) 的边永远不会相交,并且两个点集也不可能再通过连边相交。

至于 mid 怎么取?首先从 up 极角序的对应 sz 个开始找,满足 updown,middown 的夹角最小即可。

这样一定可以满足三角形内无点,且可以划分出对应大小个的点。

同时 mid 取这条重链的中点/带权中点都可以。

posted @   spdarkle  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示