析合树建树
以前一直只写过析合树计数但没见过建析合树的代码。前几天刚好碰到一个就把我的建法补在这里了。
以下称对于所有“区间 \([l,r]\)”的称呼都默认“\(p_l,p_{l+1},\cdots,p_r\) 的值形成连续段”。
考虑分治,假设现在分治到区间 \([l,r]\),如果 \(l=r\) 那么直接 return 即可。否则考虑怎样判断一个区间 \([l,r]\) 是合点还是析点。考虑 \(p\) 为最靠左的满足 \([p,r]\) 是区间的位置,那么有 \([l,r]\) 是合点当且仅当 \([l,p-1]\) 是区间,这个可以有析点和合点的定义得出。
接下来考虑怎么找出这个区间的所有儿子,还是分这个区间是析点和合点考虑:
- 如果这个区间是合点,那么我们第一次找到的这个 \([l,p-1]\) 肯定是这个区间的第一个儿子,那么对于后面的部分,我们就不断在 \([p+1,r]\) 中二分找到最左的 \(p'\) 满足 \([p',r]\) 是一个区间,然后将 \([p,p'-1]\) 设为下一个儿子然后继续向后找即可。有一个注意点,就是我们有可能找到的 \([p',r]\) 是 \([l,r]\) 最右边的儿子的某个儿子(画个图可能比较好理解),不过根据析合树的性质,跨层的连续段肯定不是区间,因此这种情况的 \([l,p'-1]\) 肯定不是区间,直接将这个作为终止条件即可。
- 如果这个点是析点,那么我们第一次找到的这个 \([p,r]\) 肯定这个区间的最后一个儿子,这次改成从右往左找到所有儿子即可。同理,我们有可能有一次二分到的 \(p\) 是 \([l,r]\) 第一个儿子的儿子节点,解决方法是如果 \([l,p-1]\) 是区间,那么说明我们已经跳到了区间 \([l,r]\) 的第一个儿子,break 掉即可。
现在我们需要进行的操作是找到 \([l,r]\) 中最左的 \(p\) 满足 \([p,r]\) 是区间。这是一个很 sb 的问题。直接将数区间问题的线段树改成主席树,然后在 \(r\) 对应的主席树上二分最左端的 \(0\) 的位置。
时间复杂度仍然是 1log 的。

浙公网安备 33010602011771号