这是一篇平衡树合并题解,平衡树合并的复杂度 并不是假的,本题复杂度 ,更通用的可以证明到 ,比如支持序列 全局加全局取膜。
https://codeforces.com/blog/entry/108601
和上面的分析相同,但受个人水平所限可能有误,还希望多多包涵,注意下文 不分。
平衡树比线段树适用性更广,常数也不大(未卡常 fhqtreap 用时 615ms)。
平衡树合并
先不考虑分裂,单说合并。
现在要合并 两棵树,选根节点堆值大的当根(假设是 ),把 的子树按照 的键值裂开(这里的裂开就是 treap 的 split),裂开的两瓣和 的左右儿子递归下去合并。
1 | int Merge(int x,int y){ |
明显是把小的树裂开更优,但是堆值大大概率就是树大的,下文也默认此情况(主要是带有随机的不会分析)。
其实这里存在一个合不合并相同结点的问题,其实是 要合并 的,因为 treap 的复杂度依赖于树和堆的唯一性,如果存在相同结点那么可能会失去性质(比如全部都一样,可以在不违反树和堆的情况下出来一条链),对于一般的 treap 不存在此问题,因为自带了一个插入时间的比较。
现在分析平衡树合并的复杂度,设第一棵树大小为 ,第二棵为 ,不难发现单次合并的复杂度 上界 为 ,大概是把小的树每个节点都拿去切割大的树,由于 finger search 所以切割复杂度为 。总复杂度为 。
为什么说是上界,因为在两颗树值域重合少的时候复杂度和最少不相交的值域段数 有关,合并一次的复杂度为 。
本题的复杂度分析
由于合并 段值域不相交的复杂度为 ,而一次 split 只会产生一段。问题是 如果先合并 再合并 会产生额外的一次 ,但是合并次数等于分裂次数,所以总复杂度为 。
更通用的复杂度分析
定义势能为 ,也就是 相邻的两个值的差的 之和。
合并 和 ,有 段值域:
形如 的是一段值域,形如 的是值域之间的距离且距离为 。
合并后 ,有
前面的把 的放在一起就是根据定义算的 ,后面的则是由于两段值域不交合并会产生新的势能,势能大小为 值域的距离也就是 。
显然有
因为 函数是下凸的,可得 ,把 里的 拿出来可得
那么把 全部拆开,可得
忽略常数可得
也就是说最多做 次 split,总复杂度为 。
带有分裂的复杂度分析
一次分裂会减少 的势能,split 是不增加势能的。
一次合并如果增加就至多增加 的势能(原因可以看上面的式子)。
那么最多就只有 的势能,而 最大就是把这些势能都减少完,所以总复杂度为 。
代码
1 | const int N = 200005; |
全局加全局取膜
全局加不影响势能。
如果取模的过程中把树分成了 段,合并后产生了至多 的势能,但是 也会变成 。把 提到外面来,显然每轮最劣的 都是一样的,因为这是一个递归的问题。原问题变成了 使得 最大。解得最小值取 ,最大值为 。所以增加的势能最大为 。
所以总复杂度为 。
通用性
确实这东西很能拓展,只要不咋影响势能都可以把合并当作基本操作,比如加、除、取膜、开根、翻转。
其实全局取膜作用与值域区间也是对的,值域区间完全可以把它当成两个独立的序列分别操作后再 merge 起来,分别操作也就是 ,完全不影响。最后只是常数段的 merge 新增的 的势能。所以只会增加总共 的势能。别的同理。