析合树
啃WC课件系列。
LCA讲得很好了~~(虽然一些奇怪的定义让人摸不着头脑)~~,为了以后复习方便自己再整理下。
析合树是用于连续段问题的比较通用的数据结构。
首先定义一下连续段:对于一个长度为
考虑到连续段有一个非常有用的性质:
对于两个相交但互不包含的连续段
, ,总有 、 、 和 都是连续段。
证明比较显然。
从这个性质出发,我们定义本原连续段为不存在和它相交且互不包含的其它连续段的连续段。容易发现一个非本原连续段一定可以用若干个相邻的本原连续段的并来表示,并且由于本原连续段互相只有包含关系,因此如果将一个本源连续段看成一个点,那么所有本原连续段实际上可以形成一棵树形结构。非本原连续段都是由某些连续的兄弟本原连续段组成的。
那么,究竟哪些连续的兄弟本原连续段可以组成非本原连续段呢?
由于所有儿子的并是一个连续段,我们不妨将儿子缩成一个点并离散化,这样可以简化为一个排列(不妨叫做儿子排列)。我们只需要知道这个排列的连续段信息即可。
一种合法的情况是,儿子排列的所有非平凡区间都不是连续段,我们将拥有这样的儿子的点成为析点。
接下来考虑至少存在一个非平凡连续段的情况,那么我们枚举所有的非平凡连续段,可以发现所有非平凡连续段的并就是整个排列,同时每一个点都会作为若干个连续段的交出现——否则我们就找到了一个非平凡的本原连续段,这是不可能的。
那么我们可以发现:假设儿子排列的长度为
于是我们可以发现这种情况下,任意一个区间都是一个连续段,我们将拥有这样的儿子的点成为合点。同时不难发现合点的儿子排列只可能是
我们还可以发现一些性质:
最后的问题是析合树的构造,这里只给出
考虑增量,在前
那么我们描述一下增量第
首先对
接下来不断重复这些步骤:
首先判断一下当前点能否成为栈顶的点的新儿子。如果可以则将当前点的父亲设为栈顶的点,并取出栈顶的点作为新的当前点。不断进行这一步直到栈为空或当前点无法成为栈顶的新儿子。
我们来证明能够合并的当前点一定是一个本原连续段。可以证明栈顶的点一定是合点(因为析点的非平凡儿子前缀一定不是连续段),因此当前点一定是作为儿子排列的最小段或最大段,不妨以是最大段为例,由于之前并未进行合并,因此当前点不存在一个不满的前缀值域是最大段的前缀,等价于不存在一个不满的后缀值域是最大段的后缀。由于最大段的前缀在之前出现过,因此如果想要和当前点的不满后缀拼合得到新的连续段,当前点的后缀的值域必须要是最大段的后缀,而这是不可能的。因此可以合并的当前点一定是本原连续段。
如果当前点不能成为栈顶的儿子,那么先通过某种方法(接下来再具体讲)判断当前点是否可能与栈顶的若干个点合并成一个连续段得到一个新点。如果不可能则直接结束这次增量。否则我们暴力向前扫描直到可以合并成一个连续段为止。
我们来证明合并的这些点一定是本原连续段。可以发现此时“切开”某个儿子的那些前缀的值域不可能是整个连续段值域的前缀(否则会出现新的本原连续段),因此不存在一个切开儿子的后缀是值域的后缀,同理也不存在一个不满的后缀是值域的前缀,因此实际上一个切开儿子的后缀是无法和之后的任何段形成连续段的,可以得证。
考虑除了判断方法之外的部分的复杂度。由于每次花费的时间和栈里减少的元素个数成正比,而每次增量最多使得栈内元素个数
最后考虑如何判断是否可能与之前的点合并。考虑预处理数组
那么问题在于如何处理
综上所述,构建析合树的复杂度可以做到