左偏树学习笔记

左偏树:能在 \(O(\log n)\) 复杂度内完成单次合并的堆

我们定义 \(dis[x]\) 表示 \(x\) 子树内 \(x\) 和最近空节点的距离 空节点 \(dis[0]=-1\)

我们让 \(dis[ls[x]]>dis[rs[x]]\) 这样左子树一定比右子树大 \(dis[x]=dis[rs[x]]+1\)

依照 Kazdale 所言:

可以把左子树当成重链 右子树当成轻链 这样把 \(y\)\(rs[x]\) 合并复杂度一定就是 \(\log\) 级别的

那么merge操作:

inl int merge(int x,int y){
    if(!x||!y)return x+y;
    if(a[y]<a[x])swap(x,y);//让y为较大值
    rs[x]=merge(rs[x],y);//y与rs[x]合并
    if(dis[ls[x]]<dis[rs[x]])swap(ls[x],rs[x]);
    //保证左偏树dis[ls[x]]>dis[rs[x]]
    dis[x]=dis[rs[x]]+1;
    return x;
}

操作需要找到当前点所在堆的根节点 那么可以记录每个点的fa暴力跳

实际上左子树深度可能是 \(O(n)\) 级别的 复杂度就回到 \(O(n^2)\)

那么可以用路径压缩并查集维护

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e6+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=1e6+3;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,m,dis[N],rt[N],ls[N],rs[N],vis[N];
inl int find(int x){return rt[x]==x?x:rt[x]=find(rt[x]);}
struct node{
    int v,id;
    friend bool operator<(node a,node b){return a.v^b.v?a.v<b.v:a.id<b.id;}
}a[N];
inl int merge(int x,int y){
    if(!x||!y)return x+y;
    if(a[y]<a[x])swap(x,y);
    rs[x]=merge(rs[x],y);
    if(dis[ls[x]]<dis[rs[x]])swap(ls[x],rs[x]);
    dis[x]=dis[rs[x]]+1;
    return x;
}
signed main(){
    n=read();m=read();dis[0]=-1;
    for(int i=1;i<=n;i++)a[i]={read(),i},rt[i]=i;
    while(m--){
        int op=read();
        if(op==1){
            int x=read(),y=read();
            if(vis[x]||vis[y])continue;
            x=find(x),y=find(y);
            if(x==y)continue;
            rt[x]=rt[y]=merge(x,y);
        }else{
            int x=read();
            if(vis[x]){writel(-1);continue;}
            x=find(x);
            writel(a[x].v);
            vis[x]=1;
            rt[ls[x]]=rt[rs[x]]=rt[x]=merge(ls[x],rs[x]);
        }
    }
    return 0;
}

实际上还可以启发式合并:每次把大的合并到小的上

那么每次一个堆被合并 \(siz\) 至少 \(\times 2\)

每个元素最多被合并 \(\log n\) 次(再多就超出 \(n\)

每次合并插入复杂度 \(\log n\)

\(n\) 个元素 复杂度 \(O(n\log^2 n)\) 同样优秀

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inl inline 
#define int ll
#define gc getchar
#define pc putchar
const int N=1e5+5;
const int M=1e8+5;
const int inf=0x3f3f3f3f;
const int mod=1e6+3;
inl int read(){
    int x=0,f=1;char c=gc();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
    return x*f;
}
inl void write(int x){
    if(x<0){pc('-');x=-x;}
    if(x>9)write(x/10);
    pc(x%10+'0');
}
inl void writei(int x){write(x);pc(' ');}
inl void writel(int x){write(x);pc('\n');}
int n,m,dis[N],rt[N],vis[N],siz[N];
inl int find(int x){return rt[x]==x?x:rt[x]=find(rt[x]);}
struct node{
    int v,id;
    friend bool operator<(node a,node b){return a.v^b.v?a.v>b.v:a.id>b.id;}
};
priority_queue<node>q[N];
inl void merge(int x,int y){
    if(siz[x]<siz[y])swap(x,y);
    siz[x]+=siz[y];rt[y]=x;
    while(!q[y].empty()){
        node xx=q[y].top();q[y].pop();
        q[x].push(xx);
    }
}
signed main(){
    n=read();m=read();
    for(int i=1;i<=n;i++)q[i].push({read(),i}),rt[i]=i,siz[i]=1;
    while(m--){
        int op=read();
        if(op==1){
            int x=read(),y=read();
            if(vis[x]||vis[y])continue;
            x=find(x),y=find(y);
            if(x==y)continue;
            merge(x,y);
        }else{
            int x=read();
            if(vis[x]){writel(-1);continue;}
            x=find(x);
            writel(q[x].top().v);
            vis[q[x].top().id]=1;
            q[x].pop();
        }
    }
    return 0;
}
posted @ 2023-11-23 20:12  xiang_xiang  阅读(20)  评论(0)    收藏  举报