从 dfs 序求 lca 到虚树到树分块 学习笔记

前言

想象我在口胡三样我都不熟悉的东西并尝试称之为“学习笔记”。

其实不过是我自己对于它的一点小理解,甚至可能是错误的!

无所谓,口胡!口胡!口胡!口胡!口胡!

一些备注

\(dfn_u\) 为点 \(u\) 的 dfn 序,\(nfd_i\) 表示第 \(i\) 个 dfs 到的点是啥(前者的反数组)

dfs 序求 lca

这个很简单,想象把点按照 dfs 序重排后求 lca,设两点为 \(u≠v\),则 lca 为:

它们 dfs 序区间内深度最小的点的父亲。

这个 ST 表搞一下即可 \(O(1)\) 查询 lca!

证明:由于 \(u,v\) 必然分别在 lca 的两个子树中间,想象过程中必然存在一个跳越子树的过程,跳到新的子树的根,其父亲就是 lca。

(想象这里有图片)

相应的也有这样的结论:

\(u,v\) 间(dfs 序上)所有的相邻点对的 lca,深度或者 dfs 序最小的就是了。

证明同理,想象中间跳子树的过程。

后面这个没啥直接用处但是等会要用。

假的,它告诉我们:dfn 序上 lca 和 min 的性质较相似。

虚树

想象一类树形问题,每次只取出树上一小部分点进行询问,询问次数和点总和皆是 \(O(n)\)

此时不能对整棵树处理,只能取出他们中的一部分点建树,这就是虚树了。

但是只用给出的 \(k\) 点建树是不够的,下图这样的信息就保存不下来了:

(想象这里有图片)

那么我们还应该保证它们的所有 lca 都在虚树上。

然后根据前面结论 2,你只需要取出 dfn 上所有相邻点对的 lca(共 \(k-1\) 个),加上一个根节点,共 \(2k\) 个点就行了。

然后具体建边方法很多,我比较喜欢按欧拉序排序的方式。

这个后面再来补充一下!

树分块

虽然说,树分块的建树过程模板是 P2325 [SCOI2005] 王室联邦,但是我从这个角度切入树分块就糊涂了,可能是我太菜!

其实树分块的理解方式是:

选出一些点 \(a\),建一棵虚树,并把虚树上所有点视为 关键点,此时我们发现,任意一条关键点上的边,对应着原来树上这两个点之间的路径和一些小枝枝(伸不出去的)。

这棵树被叫做原树的伸缩树

那么此时就发现一条原树上的路径变成了现在三个部分:

\(u\) 到某个关键点 + 伸缩树上两个关键点之间的路径 + 关键点二 到 v 的路径

那么这看起来很像分块“大段维护,局部朴素”的思想,所以我们只需要让伸缩树上每条边对应的原树边集尽量平衡就行了。

一般来说平衡应该是 \(B=\sqrt{n}\),如何达到这个平衡呢?

讲两个方法!

随机撒点

是的!随机撒 \(O(B)\) 个点建出虚树(当然他们的 lca 也是要包含的),对于大多数问题来说,只包含对于链的询问,不需要特别的性质(比如板子题就不需要了),这个够用了。

Top Cluster

想象回到 P2325 [SCOI2005] 王室联邦,现在来讲一下这道题目的做法:

dfs 保证每个子树只剩下残余的 \(<B\) 个点,那么简单合并,每当超过了 \(B\) 个就把他们视作一个新的块,最后必然剩下 \(<B\) 个点视作新的残余子树,递归上去解决问题。

就搞定了。

但是直接这样发现每个块有不止两个向外连的节点,并不符合树分块的伸缩树性质。

有两个解决方案:第一个,把这个块建出虚树,虚树上每一条边都改成一个新块,就行了,现在每个块只有 \(2\) 个关键点了。

第二个,直接分的时候,如果加入之后产生了第三个关键点,就不加了,直接把当前的点成一块。

这两个做法其实差不多的,而且可以证明这样分是平衡的,但是我不会证明。

后记

图还没搞,先发布吧。

posted @ 2024-07-02 14:08  Fun_Strawberry  阅读(45)  评论(0)    收藏  举报