树上 DS 学习
树链剖分就不说了。
全局平衡二叉树还是值得分享的。
全局平衡二叉树
对于轻重链剖分中,整棵树的结构仍然不太美观,通过全局平衡二叉树可以让树高变成 \(O(\log n)\)。
对于每条重链,取带权中点为根,然后两侧递归建树,类似 LCT 中通过 Splay 维护的手段。
那么有证明,在这棵辅助树上树高不会超过 \(O(\log n)\)。
全局平衡二叉树不太好进行子树操作。
链修改,考虑从 \(x\) 不断跳链,每跳一个结点就相当于给一个右子树打上一个标记,然后不断的跳即可,如果有标记,可以提前在这条链上释放掉所有标记再进行修改。
链查询,也是类似的过程,释放掉标记后查询即可,很类似线段树的查询过程。
由于辅助树树高 \(O(\log n)\),所以都是单 \(\log\) 的,模板题是动态 DP,只要会了如何树链剖分做就会如何全局平衡二叉树做。
静态 Top Tree
快进到正题。
为啥要学全局平衡二叉树呢,因为全局平衡这个方法需要用在静态 Top Tree 里面。静态 Top Tree 的静态只是指无法维护树形态改变的操作,而不是指无法维护修改。
考察你是如何用线段树刻画序列形态的,本质上我们就是维护了一棵区间合并树,将合并的过程构成了一棵树,如此优美的性质可以支持其完成序列上诸多复杂的操作,我们希望让树也用这样的方式刻画出来,树收缩理论应运而生,具体来说:
我们定义簇为树收缩理论中的结点,基簇为树最开始是被划分的形态,刻画形式可以变成 \((u, v, \{E(u, v)\})\),其中 \(u, v\) 是树上每一条边。在此需要说明一下簇到底是什么,簇对应着目前树中的每一条边,一个簇中包含了需要原树中的边(这些边构成一个树上连通块,当然在这里是以边为考虑重心而不是以点),定义一个簇的两个界点为对应边的两个端点。定义两种收缩操作在基础的基簇之上做合并操作(顾名思义,树的形态会被收缩):
- \(\text{Compress}(x)\),假设现在 \(x\) 是一个二度点,存在 \(x \to u, x \to v\) 这两条边,这个操作会将这两条边对应的簇进行合并,相当于将 \(x\) 这个点办掉。
- \(\text{Rake}(x)\),假设现在 \(x\) 是一个一度点,存在 \(x \to u\) 这条边,不难发现,当图中边数 \(> 1\) 时,总存在一个点 \(v\) 满足 \(u \to v\),此时将 \(x \to u\) 和 \(u \to v\) 这两个簇进行合并。
不难发现,消去一度点和二度点一定能让最终的树结构变成一条边,收缩过程中所有的簇都能被刻画成一片连通区域,这完美符合我们对树结构最初的构想。
将基簇作为 Top Tree 的叶子结点,那么 Top Tree 上每个非叶子结点都由其两个儿子做收缩操作得到,同时,将根结点对应的簇称为根簇。
但是你发现一件很不好的事情是这个 Top Tree 树高为 \(O(n)\),而且实际写起来也不好处理,我们考察如下构造:
- 将整棵树进行轻重链剖分,那么 \(\text{Compress}\) 操作实际上是对于重链上的边进行合并操作,\(\text{Rake}\) 操作实际上是对于重链上每个点的所有已经被合并成一整个簇的轻儿子做合并操作,全部合并到重儿子上来。
回忆全局平衡二叉树的平衡方式,容易想到此时对于每个点的轻儿子进行带权分治合并建树,对于重链继续带权分治合并建树,那么 Top Tree 树高就是 \(O(\log n)\) 级别的了。
接下来我们只需要像线段树一样将一条链,一棵子树拆分到有限个簇上,并且支持快速维护簇信息,就大功告成了。
对于一个簇,为了方便,我们只维护其对应的那条边的两个端点的簇路径的信息,容易发现一条链始终都能被若干个簇路径的并得到。那么构造方式就是,找到对应的基簇(对于一条链,不妨令边为两侧端点),求取在 Top Tree 上的 LCA,对应结点的簇中一定只有一个结点是其两个子簇的交,那么将路径划分为两半之后,拆分到子簇中处理这个问题,就可以将一条链拆分到 Top Tree 上 \(O(\log n)\) 个结点了。
对应子树,就更简单了,查找对应边两个端点深度较浅的那个比给定查询点深的所有极大的簇即可,因为是用重链刻画的所以肯定都在子树里。
注意,类似线段树,维护极大区间需要的代价就是需要懒标记或者永久化,不要忘记这一点。
对于维护簇路径信息,发现其和线段树上区间合并基本没有本质区别,直接维护即可。
动态维护直径
考察每一个簇,维护其中点到两个界点的最大值和簇内路径的最大值,类似最大子段和合并,可以处理边权为负的情况。
动态维护重心
Top Tree 上二分,每次走权值较大的那个儿子,必然会走到重心,注意权值并非指的是簇中的边构成的权值,而是整个结构一起构成的大小。
Top Tree 分治
Top cluster 分块
传统树分块是随机 \(\sqrt n\) 个点,然后划分成若干块,这样子干其实风险很大,常数非常大.
标准的树分块是基于 Top cluster 的分块,即在我们构建的平衡的 Top Tree 中将所有大小 \(\le B\) 的极大簇拿出来,根据结构,其必定内覆盖满每个点以及每条边,并且个数位 \(\frac{n}{B}\) 级别,当然,若是为了 Top cluster 单独写个求出 Top Tree,那就太划不来了,论文里有一种简单的方法进行分块.
用一个栈记录还为被划分的边,假设现在在 \(x\) 结点进行 DFS,如果满足以下条件之一,那么 \(x\) 为一个界点:
- \(x\) 为根.
- \(x\) 有至少两个儿子子树内有界点.
- 栈的大小超过过 \(B\).
如何决定哪些边属于一个簇,对于每个儿子,可以构成两个序列,一个序列记录每个儿子子树中没有确定边的个数,第二个序列记录子树中是否有界点,那么现在问题就变成了划分成若干个连续段,使得每一段没有确定边的个数都是 \(\le B\) 的,并且存在一个界点,根据论文中的结论,暴力贪心即可,一定是对的.
注意到相邻两个簇一定会有一个交界点,那么只需要记录每个簇深度最浅的那一个界点即可.
有时间会把 Top Tree 和 Top cluster 分块写一下的.

浙公网安备 33010602011771号