左偏树get!
可并堆望文生义就是可以快速合并的堆,左偏树是其中的一种。
首先放开左偏树不谈,我们想想如何合并两个堆(x)和(y)(小根堆):
- 首先比较两个堆的堆顶,我们默认(x)的堆顶元素更小(否则的话交换(x、y)就好了),那么(x)的堆顶显然是新堆(指合并后的堆)的堆顶。
- 然后的话我们可以将(y)随便与(x)的一个儿子合并,并递归这个过程。
那么如果这棵树退化成链的话就凉了,所以左偏树就登场啦:
- 左偏树每个节点记四个信息(l,r,val,dis),分别表示左右子树,权值,和到左偏树最近的叶子节点的距离。
- 任意节点的左子树的(dis)值大于等于右子树的(dis)值(也就是左偏树的字面意思啦)。
- 任意节点的(dis)值都是(O(logn))级别的,大致证明:可以认为左偏树的堆顶的(dis)值就是一直向右子树走的那条链的长度,并且由于左偏树的性质我们知道一棵左偏树的左子树的右链的长度一定不会小于右子树右链的长度,然后再大致感性理解一下,我们发现一棵堆顶(dis)为(n)的左偏树它的节点数是(O(2^n))级别的(节点最少是是一棵完全二叉树),也就证明了上述结论。
这样多记一个(dis)有什么用处呢,我们发现这样我们在上述(暴力合并堆)第二步的时候可以钦定(y)和(x)的右子树合并,这样每合并一次当前点的(dis)值必然减少(1),而(dis)是(O(logn))级别的,所以可以做到(O(logn))合并。
删堆顶的操作相当于先将堆顶的两个儿子合并起来。
注:左偏树的树高并没有保证,最坏情况下可能达到(O(n)),所以在查找某个点属于哪棵子树的时候不能暴力跳,可以使用并查集来维护这个东西。
例题:
- luogu P3377 【模板】左偏树(可并堆) 题解(模板代码可以在这里看)