析合树学习笔记
先放dalao的blog link
定义
对于一个排列定义一个值域连续段
为一个子区间\([l,r]\)
满足\(max(a[l]...a[r])-min(a[l]...a[r])=r-l\)
即把这个子区间排好序它们是连续的\((r-l+1)\)个正整数
那么我们容易发现一些性质
对于两个连续段,它们的交一定是一个连续段
对于两个有交的连续段,它们的并一定是一个连续段
我们定义一个连续段是本原连续段,当且仅当满足不存在和它相交却没有包含关系的连续段
容易发现这样的连续段构成了一棵树形结构
接下来我们定义树上的析点和合点
如果把树上一个点x的所有儿子区间\([l_1,r_1],[l_2,l_2]...[l_k,r_k]\)拿出来(这里是下标区间)
这些子区间是一个一个的连续段
把他们按照在序列内的顺序排好序(即\(l_{i+1}=r_i+1\))
那么如果满足\(max(a[l_i]...a[r_i]) \le min(a[l_{i+1}]...a[r_{i+1}])\)
即这些子区间对应的值域是连续的
我们就认为x是合点
否则x是析点
在此基础上
我们可以发现
合点的任意大于一个在下标上连续的儿子他们拼起来依然是连续段
析点的任意大于一个在下标上连续的儿子他们拼起来,除了所有儿子拼起来以外,其它的都不是连续段
第一点非常显然
第二点可以考虑反证法
假设存在我们取最长的一段
那么我们发现不存在与它相交且不包含的值域连续段(不然就不是最长的了)
那么这一段应该是一个本原连续段
这与这是几个不同的点矛盾
进而我们可以得到
析合树可以构成所有连续段,且所有连续段在析合树上一定是以下两种形式:
1.树节点构成的连续段
2.合点的非平凡儿子区间构成的连续段
同样使用反证法可以证明
构造
基本方法:
考虑增量,假设做到i,已经得到了\([1,i-1]\)的析合森林,用一个栈维护这个析合森林
用下面的方法进行增量:
设置一个当前点now,初始为\([i,i]\),考虑栈顶的点为top
1.如果top是合点,并且now可以作为它的最后一个儿子,那么将now设置为top的最后一个儿子,弹出top为新的now,重复这个过程
2.否则从top开始往回找到最少的栈顶的若干个点可以和now组成连续段,弹出他们,新加一个点向这些点和原来的now连边,将now设置为新点,重复这个过程
这里注意2里如果只需要找栈中的一个点那么新加的这个点是个合点,否则是析点
直到栈为空或2找不到
复杂度分析:
1是\(O(n)\)的,在2中被合并掉的点是\(O(n)\)的,但是如果到最后都无法合并,复杂度可能会退化到\(O(n^2)\),所以需要一个\(L_i\)表示以i为右端点的连续段的最远左端点,超过了就break就\(O(n)\)了
如何求\(L_i\):
线段树对\(r\)动态维护\(max(l,r)−min(l,r)−(r−l)\)
他的最小值一定是0
线段树上取最前面的满足最小值是0的l即可
max和min可以利用单调栈维护
例题
CodeChef March Challenge 2020 Good Subsequences
建出析合树利用连续段的性质
发现可能的一对一定都是析合树上某个合点的一段连续的儿子区间
建出析合树随便算算即可
Comet OJ - Contest #6 permutation
数析合树可还行
自己的题解在这里link