左偏树/可并堆
1.什么是左偏树?
上面的树都是左偏树。
先引出一个概念,dis等于节点到它子树里面最近的叶子节点的距离,特别地叶子节点的dis等于0。
观察上图我们可以感性理解左偏树,就是左子树的深度大于等于右子树,看上去整个树向左偏。
再看一眼就可以总结出几条性质:
1.左儿子的dis<=右儿子的dis(左偏性质)
2.节点的dis=右儿子的dis+1(因为存的是最近的叶子节点,右边的叶子一定离得更近)
3.每个节点的值一定小于等于儿子节点的值(堆性质)
不要问为什么,只有满足这些条件的才叫左偏树。
同时由上面的性质可以推出:任何时候,节点数为n的左偏树,距离最大为log(n+1)−1。
证明:
对于一棵dis=k的树,需要的最少的节点数是满二叉树(少一个点dis就等于k-1)。
若一棵左偏树的距离为k,则这棵左偏树至少有2k+1−1个点。
n>=2k+1-1
n+1>=2k+1
log(n+1)>=k+1
log(n+1)-1>=k
因为上面这个性质可得左偏树的深度有限制,两个左偏树进行合并可以用Θ(logn)的复杂度。
2.基本操作
1.merge(合并)
两个左偏堆合并,要满足堆的性质,所以从两个根中选出小的一个当根,大的那个与右子树合并即可递归到只有一或两个节点的情况。
因为深度最大为log(n+1)所以一次复杂度Θ(logn)。
inline int merge(int x,int y) { if(!x||!y)return x+y;//边界情况 if(dui[x].v>dui[y].v||(dui[x].v==dui[y].v&&x>y))swap(x,y);//使x为小的那个的根 rs=merge(rs,y);//递归,将y与右子树合并 if(dui[ls].dis<dui[rs].dis)swap(ls,rs);//堆建完后,保证左偏堆性质交换左右子树 dui[ls].rt=dui[rs].rt=dui[x].rt=x;//更新根 dui[x].dis=dui[rs].dis+1;//更新dis return x; }
2.pop(删除x节点所在堆的最小值/最大值)
inline void pop(int x) { dui[x].v=-1; dui[ls].rt=ls;dui[rs].rt=rs;//更新左右子树的根 dui[x].rt=merge(ls,rs);//将左右子树合成新的堆 }
3.Del:(删除任意(x)编号节点)
将x删掉,将x的左右儿子合并,然后接到f[x]的儿子处。
因为这时可能不满足节点的dis=右儿子的dis+1的性质。
所以向上更改。
void pushup(int x) { if(x==f[x])return ;//达到根节点,返回 if(t[x].d!=t[rs(x)].d+1)//不满足左偏性质,更新 { t[x].d=t[rs(x)].d+1; pushup(f[x]); } } void del(int x) { int fx=f[x];//x的父亲 int u=merge(t[x].ch[0],t[x].ch[1]);//合并左右儿子 f[u]=fx;//更新合并后的节点的信息 if(t[fx].ch[0]==x)t[fx].ch[0]=u; else t[fx].ch[1]=u; t[x].val=t[x].ch[0]=t[x].ch[1]=t[x].d=0; pushup(x);//遍历检查左偏性质 }
4.Push(插入x节点)
新建一个节点,将其初始化为x
因为这个节点也可以视为一个堆,可以直接合并
5.并查集存堆找根并路径压缩
inline int get(int x) { return dui[x].rt==x?x:dui[x].rt=get(dui[x].rt); }
3.例题:
https://www.luogu.com.cn/problem/P3377
#include <bits/stdc++.h> using namespace std; #define ls dui[x].son[0] #define rs dui[x].son[1] int n,m; struct node { int son[2],rt,v,dis; }dui[100010]; inline int merge(int x,int y) { if(!x||!y)return x+y; if(dui[x].v>dui[y].v||(dui[x].v==dui[y].v&&x>y))swap(x,y); rs=merge(rs,y); if(dui[ls].dis<dui[rs].dis)swap(ls,rs); dui[ls].rt=dui[rs].rt=dui[x].rt=x; dui[x].dis=dui[rs].dis+1; return x; } inline int get(int x) { return dui[x].rt==x?x:dui[x].rt=get(dui[x].rt); } inline void pop(int x) { dui[x].v=-1; dui[ls].rt=ls;dui[rs].rt=rs; dui[x].rt=merge(ls,rs); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&dui[i].v); dui[i].rt=i; } for(int i=1;i<=m;i++) { int op,x,y; scanf("%d%d",&op,&x); if(op==1) { scanf("%d",&y); if(dui[x].v==-1||dui[y].v==-1)continue; int f1=get(x),f2=get(y); if(f1==f2)continue; else merge(f1,f2); } else { if(dui[x].v==-1)printf("-1\n"); else printf("%d\n",dui[get(x)].v),pop(get(x)); } } return 0; }
4.最后
本文是在阅读ShuraEye和_Orchidany的大作后有感而发