羲和树
简介
析合树是用于处理序列上一类特定结构的数据结构与思想。
这类结构可能具有如下特征:
-
要么包含,要么不交。
-
下标和值域的连续性。
如果有题目出现了上述两点特征,就可以往析合树的想法上靠。
概念
段
对于一个 \(n\) 阶排列 \(p\),定义其中的一个段为 \(p\) 的一个非空区间 \([l,r]\),满足 \(p_l \sim p_r\) 所组成的集合在值域上是连续的。
若一个段 \(A\) 满足:不存在其余任何一个段 \(B\),使得 \(B\) 与 \(A\) 有交且不包含。那么称 \(A\) 为排列 \(p\) 的本原(神)连续段。
任意一个长度 \(>1\) 的本原连续段都可以被划分为若干个不交的本原连续段。
所有的本原连续段显然构成了一棵树的结构,这就是所谓的析合树。
合点
在析合树上,若一个节点的所有儿子是有序的,那么我们称这个点是合点。
这里的有序只考虑儿子与儿子之间的相对顺序,而不考虑单个儿子内部的顺序。
例如区间 \([1,5]\) 是一个本源连续段,其值域也为 \([1,5]\),它能够被划分为 \([1,1],[2,4],[5,5]\) 值域也分别为 \([1,1],[2,4],[5,5]\),那么他就是一个合点。
无论儿子 \([2,4]\) 内部的排列是 \(\{4,3,2\}\) 还是 \(\{3,4,2\}\) 都与 \([1,5]\) 是否为合点无关。
显然,若一个点仅有两个儿子,则其必为合点。
析点
同理,若一个点的儿子无序,则该点为析点。
特别地,我们钦定叶子节点是析点。
建立
段的判定
考虑如下过程:从左到右枚举右端点 \(r\),需要实时维护出有哪些左端点 \(l\),使得 \([l,r]\) 是一个段。
区间 \([l,r]\) 是段的充要条件可以写作 \(r-l=\max_{i=l}^r p_i - \min_{i=l}^r p_i\)。
维护以当前右端点为结尾的每个后缀的极值是单调栈的经典运用。
因为显然有 \(\max_{i=l}^r p_i - \min_{i=l}^r p_i+l \geq r\),令 \(f(l)=\max_{i=l}^r p_i - \min_{i=l}^r p_i+l\),我们只需要用线段树维护 \(f\) 的最小值即可。
过程
维护三个栈,其中两个用于计算最值,令一个用来存储当前还没有父节点的段的信息。
从左到右扫描,设当前在点 \(r\),维护 \(now\) 表示当前要处理的段,初始设为 \([r,r]\)。
然后在线段树上查找最小的 \(l\),使得 \([l,r]\) 是一个段。
接下来进行构造:
下面所指的合并只要求整体是段即可,而拼接还要求有序。
-
若栈顶的点是合点,并且其顺序能与 \(now\) 拼接,那么就把 \(now\) 拼接在栈顶段上,并更新 \(now\) 为栈顶段,弹出栈顶。
-
否则判断栈顶点能否与 \(now\) 合并,如果可以就弹出栈顶,新建一个合点,把 \(now\) 设为这个合点。
-
再否则就一直弹栈,直到整体能够合并,新建一个析点,把 \(now\) 设为这个析点。
重复以上判断,直到栈顶的左端点是 \(<l\) 就停止。
最后把 \(now\) 加入栈。
注意,析合树的点数是 \(2n\) 的,数组大小需开两倍。
例题
\(\text{Intrinsic Interval}\)
考虑先建出析合树。
一个合点的儿子要么是析点,要么是反方向的合点。
所以对于查询 \([x,y]\),直接找到 \(x\) 和 \(y\) 的 \(\text{LCA}\),若其为析点,则答案就是 \(\text{LCA}\)。
否则就是 \(x\) 和 \(y\) 所对应的两个儿子的区间。
\(\text{Good Subsegments}\)
建出析合树后,考虑 \(x\) 和 \(y\) 到 \(\text{LCA}\) 的链。
对于链上的合点,记其完全被包含在询问区间内的儿子个数为 \(c\),则其贡献的方案数为 \(\binom c 2\),还要再加上这些儿子各自内部的所有贡献。
若为析点,则只需要加上儿子贡献即可,如果完全包含该析点要额外 \(+1\)。
这道题还有更好想的历史和线段树做法。

浙公网安备 33010602011771号