通常我们使用的二叉堆,是用一个数组实现的完全二叉树,对于查询有O(1)复杂度,对于插入、删除有O(logn)复杂度
而且常数比较小,是一个比较优秀的数据结构
但是。
当我们需要合并两个堆的时候,用普通的二叉堆就显得吃力了,时间复杂度达到O(mlogn)
这个时候可并堆这种数据结构就出现了。而在其中,尤以左偏树代码复杂度和时间复杂度综合得比较优秀
左偏树
在介绍左偏树之前,得先引入一个叫距离(d)的概念:
在左偏树中,一个节点u的距离,等于u到它和叶子节点中最近的左右儿子不满的节点【也就是可插入的节点】的距离。
然后左偏树是这样定义的:
左偏树是左儿子距离不小于右儿子距离的二叉堆。
这样子有什么用呢?
当插入节点时,只需往右儿子插入,可降低插入的时间复杂度。
实现
首先每个节点这样声明:
struct LT{ int v,l,r,d; LT() {l=r=d=0;} }e[maxn];
左偏树的所有操作都是以合并操作为基础的:
对于根节点为u和v两个左偏树是这样合并的:
1、如果其中有空节点,直接返回另一个非空结点
2、选择节点值较小的作为根节点,然后合并根节点的右儿子和另一个节点【merge(root.r,v)】【递归】
3、维护根节点的距离。我们设空节点的距离为-1,那么根节点的距离=右儿子距离+1
C++代码:
int merge(int a,int b){ if(!a) return b; if(!b) return a; if(e[b].v<e[a].v) a^=b^=a^=b; e[a].r=merge(e[a].r,b); if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r); if(!e[a].r) e[a].d=0; else e[a].d=e[e[a].r].d+1; return a; }
有了merge操作其他一切操作就都特别简单了:
insert操作:先将建立一个新节点,再将新节点看做一棵树与原树合并。
del操作:合并根节点的左右子树
void insert(int x){ int b=++siz; e[b].v=x; if(!rt) rt=b; else rt=merge(rt,b); }
void del(){rt=merge(e[rt].l,e[rt].r);}
左偏树其实甚至可以当普通的二叉堆使用,只不过常数大一些,由于编程难度不高,但有时用的甚至可以成为替代二叉堆的一个选择。
洛谷PP3377【模板】可并堆(左偏树)
一道板题
但用了左偏树+并查集
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int maxn=100005,INF=2000000000; inline int read(){ int out=0,flag=1;char c=getchar(); while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();} while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();} return out*flag; } int N,M; struct LT{ int v,l,r,d,f; LT() {l=r=d=f=0;} }e[maxn]; int merge(int a,int b){ if(!a) return b; if(!b) return a; if(e[a].v>e[b].v||(e[a].v==e[b].v&&a>b)) swap(a,b); e[a].r=merge(e[a].r,b); e[e[a].r].f=a; if(e[e[a].l].d<e[e[a].r].d) swap(e[a].l,e[a].r); e[a].d=e[e[a].r].d+1; return a; } void del(int r){ e[e[r].l].f=e[e[r].r].f=0; merge(e[r].l,e[r].r); e[r].v=-1; } int find(int u){ if(!e[u].v==-1) return 0; while(e[u].f) u=e[u].f; return u; } int main() { e[0].d=-1; N=read(); M=read(); for(int i=1;i<=N;i++) e[i].v=read(); int cmd,a,b,fa,fb; while(M--){ cmd=read(); if(cmd&1){ a=read(); b=read(); if(e[a].v==-1||e[b].v==-1) continue; fa=find(a); fb=find(b); if(fa!=fb) merge(fa,fb); }else{ a=read(); if(e[a].v==-1) printf("-1 "); else{ fa=find(a); printf("%d ",e[fa].v); del(fa); } } } return 0; }
洛谷P1456 Monkey King
差不多的题,左偏树+并查集
【猴子找朋友来帮忙自己却不打= =】
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=100005,INF=2000000000; inline int read(){ int out=0,flag=1;char c=getchar(); while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();} while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();} return out*flag; } int N,M; struct LT{ int v,l,r,d; LT() {l=r=d=0;} }e[maxn]; int fa[maxn]; int find(int x){return fa[x]==x ? x:fa[x]=find(fa[x]);} int merge(int u,int v){ if(!u) return v; if(!v) return u; if(e[u].v<e[v].v) u^=v^=u^=v; e[u].r=merge(e[u].r,v); fa[e[u].r]=u; if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r); e[u].d=e[e[u].r].d+1; return u; } int del(int u){ int lc=e[u].l,lr=e[u].r; e[u].l=e[u].r=0; fa[lc]=lc;fa[lr]=lr; return merge(lc,lr); } int main() { e[0].d=-1; int a,b,Fa,Fb; while(cin>>N){ for(int i=1;i<=N;i++) {e[i].v=read();e[i].l=e[i].r=e[i].d=0;fa[i]=i;} M=read(); while(M--){ a=read(); b=read(); Fa=find(a); Fb=find(b); if(Fa!=Fb){ a=del(Fa); b=del(Fb); e[Fa].v>>=1; e[Fb].v>>=1; a=merge(a,Fa); b=merge(b,Fb); a=merge(a,b); printf("%d ",e[a].v); }else printf("-1 "); } } return 0; }
洛谷 P1552 [APIO2012]派遣
这是一道左偏树+树上DP
用大根堆维护,将所有的儿子【包括儿子的子树】建立成堆,删去最大的直至工资在预期范围内,就可以得到以该点为管理者的最大收益。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define LL long long int using namespace std; const int maxn=100005,INF=2000000000; inline int read(){ int out=0,flag=1;char c=getchar(); while(c<48||c>57) {if(c=='-') flag=-1;c=getchar();} while(c>=48&&c<=57) {out=out*10+c-48;c=getchar();} return out*flag; } int N,M,lson[maxn],rb[maxn],root; LL dp[maxn],lead[maxn]; struct LT{ LL v; int l,r,d; LT() {l=r=d=0;} }e[maxn]; struct node{ LL v; int cnt,rt; }; int merge(int u,int v){ if(!u) return v; if(!v) return u; if(e[u].v<e[v].v) u^=v^=u^=v; e[u].r=merge(e[u].r,v); if(e[e[u].l].d<e[e[u].r].d) swap(e[u].l,e[u].r); e[u].d=e[e[u].r].d+1; return u; } int del(int u) {return merge(e[u].l,e[u].r);} node dfs(int u){ if(!lson[u]){ dp[u]=e[u].v<=M ? lead[u]:0; return e[u].v<=M ? (node){e[u].v,1,u}:(node){0,0,0}; }else{ int rt=u,cnt=1,v=lson[u]; LL s=e[u].v; node t; while(v){ t=dfs(v); cnt+=t.cnt;s+=t.v;rt=merge(rt,t.rt); v=rb[v]; } while(s>M){ s-=e[rt].v; rt=del(rt); cnt--; } dp[u]=(LL)lead[u]*(LL)cnt; return (node){s,cnt,rt}; } } int main() { e[0].d=-1; int f; N=read(); M=read(); for(int i=1;i<=N;i++){ f=read();rb[i]=lson[f];lson[f]=i; if(!f) root=i; e[i].v=read(); lead[i]=read(); } dfs(root); LL ans=0; for(int i=1;i<=N;i++) ans=max(ans,dp[i]); cout<<ans<<endl; return 0; }