左偏树

左偏树

左偏树解决的问题:高效合并两个堆。

例子:

你现在有两个大根堆,你要把这两个堆合并(合并完后还是大根堆)。

设当前两个堆的根为 \(rt1,rt2\)

假设 \(rt1\) 的值小于 \(rt2\) 的值。

那么根据大根堆的理论,\(rt1\) 应该是 \(rt2\) 的子孙。

那就出现了一个问题, \(rt1\) 是放左子树还是放右子树。

如果放左子树,那么就去合并 \(rt1\)\(rt2\) 的左子树。

如果放右子树,那么就去合并 \(rt1\)\(rt2\) 的右子树。

为了简化,那就索性做一个非常简单的决定:只放右子树。

\(f(x)=\) 从点 \(x\) 一直向右走能走的步数。

这样子合并的总次数就是 \(f(rt1)+f(rt2)\)。(两棵树都只会往右走,虽然中途有可能反复切换树根)

1788332528412.png

所以这样一来我们的目标就是最小化 \(f(rt1)+f(rt2)\),这就是左偏树的核心目标。

做法:

已知一个递推式 \(f(x)=f(rson)+1\),也就是说我们希望右子树的 \(f(rson)\) 尽量小。

那么若 \(f(lson)>f(rson)\) 显然交换左右子树更优.

于是我们就确定了左偏树最重要的性质: \(f(lson)\le f(rson)\)

时间复杂度:

\(n(d)\) 表示某个点的 \(f\) 值为 \(d\) 的子树的最少节点数。

\(n(0)=1\)

首先会发现 \(n(x)\) 这个函数是递增的,因为有一个式子是 \(n(x)\ge1+n(x-1)\)

那么对于一个节点 \(x\) , 假设其 \(f\) 值为 \(y\) ,那么右子树的 \(f\) 值必定为 \(y-1\) ,而左子树的 \(f\) 值大于等于 \(y-1\)

由于函数单调递增, 所以 \(n(y)\ge 2\times n(y-1)\)

\(f(x)\)\(log\) 级别的。

那么一次修改就是 \(log\) 级别的。

左偏树实现细节:

  • \(merge\): 就如刚刚所说。

  • \(insert\): 把新加的节点当作一个左偏树,然后直接合并

  • \(pop\): 即删除根节点,直接合并根节点的左右子树

  • \(build\): 将节点放在队列里面,每次 \(merge\) 队首的两个树,然后把合并的结果丢到队尾。

  • \(top\): 直接查询顶值就行了

代码:
struct A{
    int key,pos,dist;
    int lson,rson;
}node[N];
int f[N];//并查集
int merge(int a,int b){//合并两棵树
    if (!a) return b;
    if (!b) return a; 
    if (check(a,b)){
        swap(a,b);
    }
    node[a].rson=merge(node[a].rson,b);
    int ls=node[a].lson,rs=node[a].rson;
    if (node[ls].dist<node[rs].dist) swap(node[a].lson,node[a].rson);
    node[a].dist=node[node[a].rson].dist+1;
    return a;
}
bool vis[N];
int pop(int tp){//删除以tp为根的树
    int ls=node[tp].lson,rs=node[tp].rson;
    if (vis[tp]) return -1;//已经被删
    int res=merge(ls,rs);
    if (ls)    f[ls]=res;
    if (rs) f[rs]=res;
    if (res) f[res]=res;
    f[tp]=res;vis[tp]=1;
    node[tp].lson=node[tp].rson=0; 
    return node[tp].key;
}
int main(){
    //...
    for (int i=1,opt;i<=m;i++){
        cin >> opt;
        if (opt==1){//合并
            int l,r;
            cin >> l >> r;
            if (vis[l]||vis[r]) continue; //必要,如果任何一个已经被删除就直接跳过
            int fal=getf(l),far=getf(r);
            if (fal==far) continue;
            int t=merge(fal,far);
            f[fal]=f[far]=t;
            f[t]=t; 
        }
        else{//查询
            int x;
            cin >> x;
            if (vis[x]) cout << -1 << endl;
            else cout << pop(getf(x)) << endl;
        }
    }
    return 0;
}

posted @ 2026-09-06 18:26  wlhao  阅读(4)  评论(0)    收藏  举报