定义 & 结构
连续段
- 对于一个排列$A$,我们定义$[l,r]$是连续段,当且仅当这个区间的值域也是连续的一段。即,不存在$x,y,z$,$x,y\in [l,r]\wedge z\not\in [l,r]\wedge A_x < A_z < A_y$。也等价于$\max \{ A_k \mid k\in [l,r] \} - \min \{ A_k \mid k\in [l,r] \} = r-l$。
- 连续段有一个很好的性质:考虑两个相交且不互相包含的连续段$x,y$,总有$x\cup y$、$x\cap y$、$x\setminus (x \cap y)$、$y\setminus (x\cap y)$是连续段。
本原连续段
- 如果对于某一个连续段$[l,r]$,不存在另外任意一个连续段与它有交且不包含,那么我们称这个连续段为本原连续段。
- 由定义可以看出来,所有的本原连续段构成树形结构,每一个点的父亲是包含它的、长度最短的本原连续段。
析点、合点
考虑某一个本原连续段的儿子集合:
- 合点
- 如果这些儿子中某相邻的两个拼起来是连续段,那么这点的任意若干个位置连续的儿子拼起来得到的一定是连续段。我们称这样的点是合点。
- 合点至少有两个儿子。
- 一种证明方法是这样的:
- 假设儿子序列的长度为$n$,枚举这个序列的非平凡连续段(长度不为 $1$ 或者 $n$),那么所有的连续段的并一定是整个序列,并且序列中的每一个点都作为若干个非平凡连续段的交出现——否则就会出现非平凡本原连续段,这是不符合定义的。
- 这也就意味着,$[1,n)$的每一个点至少是一个非平凡连续段的左端点,$(1,n]$的每一个点至少是一个非平凡连续段的右端点。
- 证明 $[l,r]$ 是连续段:把以$l,l+1,l+2\cdots n-1$为左端点的非平凡连续段取并就得到$[l,n]$,把以$r,r-1,\cdots 2$为右端点的非平凡连续段取并得到$[r,1]$,再把$[l,n]$和$[1,r]$取交就可以了。
- 析点
- 任意两个相邻的儿子拼起来都不是连续段。我们称这样的点为析点。
- 析点至少有四个儿子。
- 特别地,叶子节点没有儿子,我们认为叶子节点是合点。整棵树一定恰好包含$n$个叶子节点。
另外还有一个结论:一个形态合法(包含$n$个叶子节点,点有析点和合点两种,析点至少有四个儿子,合点至少有两个儿子)的析合树唯一对应到一个集合$I$,$I$中所有的区间都是连续段并且不在$I$中的所有区间都不是连续段,并且一定可以构造出一个排列它的连续段集合是$I$。
构建方法 - 0
下面是一种空间 $O(n\log n)$,时间 $O(n\log n)$ 的构建方法。
从$[1,n]$开始递归建树。假设当前的区间是$[l,r]$,我们需要找出这个点的儿子是哪些区间,并且还需要判断这个点是析点还是合点。
首先,找出最大的$y\in [l,r)$满足$[l,y]$是连续段,找出最小的$x\in (l,r]$满足$x$是连续段。
如果$x\le y+1$,那么当前点是合点,否则,当前点是析点。
如果当前点是析点:$[l,y]$就是当前点的第一个儿子。然后再找$[y+1,r)$最靠右的$y’$满足$[y+1,y’]$是连续段段,则$[y+1,y’]$是第二个儿子。以此类推可以找出当前点所有的儿子。
如果当前点是合点:$[l,x-1]$是当前点的第一个儿子。然后找$[x’,y+1]$(注意边界不是$r$!)最靠左的$x’$满足$[x’,r]$是连续段,则$[x,x’-1]$是第二个儿子。以此类推。最后一个儿子是$[y+1,r]$。
最后还剩一个问题:如何对于$[l,r]$,找出最靠右的$x$满足$[l,x]$是连续段。
一个区间$[l,r]$是连续段等价于$\sum_{i\in [1,n)} [p_i \in [l,r] \vee p_{i+1} \in [l,r] ] = r-l $,其中$p_i$表示$i$这个值在排列中出现的位置。这个可以提前用扫描线+主席树预处理出来,查询的时候就是查询第$l$棵线段树上$r$左侧的最靠右的$1$的位置。
jzoj6202
1 |
|
构造方法 - 1
参考 OI-wiki
下面介绍一种空间 $O(n)$,时间 $O(n\log n)$ 的构造方法。
设 $L_i$ 为最小的 $l$ 满足 $[l,i]$ 为连续段。
增量法,用一个栈维护已经处理过的元素构成的析合树森林。具体来说,处理完前 $i$ 个元素之后,栈中存储了 $[L_i,i], [L_{L_i-1}, L_i - 1], [L_{L_{L_{L_i-1}}-1},L_{L_{L_i-1}}-1] \cdots$ 这些区间的析合树。
新加入一个结点(或者一个子树的根),可能的情况有:
- 栈顶元素是合点,并且新加入的结点可以作为栈顶元素的儿子
- 弹出栈顶元素,将新加入的点加入栈顶元素的儿子集合,再尝试插入栈顶元素
- 新加入的结点和栈顶元素构成了合点
- 弹出栈顶元素,新建一个合点,把新加入的结点和栈顶元素设置成它的儿子,再尝试插入新建的合点
- 新加入的元素和栈顶部的若干个元素一起构成了析点
- 弹出涉及到的栈中元素,新建一个析点,把新加入的结点和涉及到的栈中元素都设置成它的儿子,再尝试插入新建的析点
1 | // GalaxyOJ 2121. 简单数据结构题 (2020联测#6) |