析合树学习笔记

先放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

建出析合树利用连续段的性质

发现可能的一对一定都是析合树上某个合点的一段连续的儿子区间

建出析合树随便算算即可

code

Comet OJ - Contest #6 permutation

数析合树可还行

自己的题解在这里link

code

posted @ 2020-08-11 00:27  deaf  阅读(205)  评论(0编辑  收藏  举报