模板:左偏树
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模板:左偏树相关的知识,希望对你有一定的参考价值。
如果你知道priority_queue的话,那自然就知道左偏树的目的了。
左偏树的目的和优先队列一致,就是求出当前所在堆中的最大(小)值。
但是我们作为高贵的C++选手,我们为什么还要学习左偏树呢。
当然是因为priority_queue太!慢!了!
————————————————————————————————————
概念引入:
对于左偏树,我们引入两个概念:
外节点:如果该节点的左子树或右子树为空,那么该节点为外节点。
距离(dis):该节点到达最近的外节点经过的边的个数。
我们同时将优先队列的定义照搬过来:
键值(val):节点的权值。
同时将优先队列的性质照搬过来:
性质1:节点的键值小于或等于它的左右子节点的键值。
对于左偏树,赋予它一个性质:
性质2:节点的左子节点的距离不小于右子节点的距离。
同时可以推出:
性质3:节点的距离等于它的右子节点的距离加1。(显然)
为了满足性质3,我们规定空节点的dis为-1。
此外还有很多性质,不过都是跟推左偏树复杂度相关的性质,有兴趣可以百度。
——————————————————————
复杂度:
这里直接给出左偏树的复杂度:
合并:O(logN1+logN2)(N1和N2分别为待合并左偏树的节点个数)
插入:O(logn)
删除最小节点:O(logn)
建树:O(n)
删除已知编号节点:O(logn)
————————————————————
基本操作:
以建立大根堆为例,现将前三个操作说明。
合并:
按照左偏树的定义合并即可。
设要合并的两个堆的根节点为x,y。
如果其中一个堆为空堆,则不需合并。
否则比较它们的val,设x为val较小的堆根节点。
则x显然是新堆(子堆)的根节点,但是y在哪里我们并不知道。
为了满足左偏树的性质(不提供证明),我们将x的右子堆和y堆合并,最后按照它们的dis交换一下左右儿子即可。
插入:
将插入元素看成堆的话,与合并相同。
删除最小节点:
将根节点删除后,其左右子堆合并即可。
————————————————————————————————
学到这里代码实现不是很难,这里提供板子题目:
洛谷P3377:[模板]左偏树(可并堆):https://www.luogu.org/problemnew/show/3377
代码如下:
#include<cstdio> #include<cmath> #include<algorithm> #include<cstring> #include<iostream> #include<cctype> #include<queue> using namespace std; const int N=1e5+3; inline int read(){ int X=0,w=0;char ch=0; while(!isdigit(ch)){w|=ch==‘-‘;ch=getchar();} while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar(); return w?-X:X; } int fa[N]; struct tree{ int l,r; int dis,val; }tr[N]; int merge(int x,int y){//两个堆的根,返回新堆的根 if(x==0||y==0)return x+y;//其中一个是空堆 if(tr[x].val>tr[y].val||(tr[x].val==tr[y].val&&x>y)) swap(x,y);//让x的价值比y的价值小,这样x就在y上面 tr[x].r=merge(tr[x].r,y);//合并x右树和y fa[tr[x].r]=x; if(tr[tr[x].l].dis<tr[tr[x].r].dis) swap(tr[x].l,tr[x].r);//让右儿子更接近外节点 tr[x].dis=tr[tr[x].r].dis+1;//根据右儿子dis更新节点dis return x; } int get(int x){ while(fa[x])x=fa[x]; return x; } void del(int x){ tr[x].val=-1; fa[tr[x].l]=fa[tr[x].r]=0; merge(tr[x].l,tr[x].r); return; } int main(){ int n=read(); int m=read(); for(int i=1;i<=n;i++)tr[i].val=read(); for(int i=1;i<=m;i++){ int c=read(); if(c==1){ int x=read(); int y=read(); if(tr[x].val==-1||tr[y].val==-1||x==y)continue; int nx=get(x); int ny=get(y); merge(nx,ny); }else{ int x=read(); if(tr[x].val==-1){ puts("-1"); }else{ int y=get(x); printf("%d\n",tr[y].val); del(y); } } } return 0; }
以上是关于模板:左偏树的主要内容,如果未能解决你的问题,请参考以下文章