左偏树——可以标记合并的堆
Posted miracevin
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了左偏树——可以标记合并的堆相关的知识,希望对你有一定的参考价值。
左偏树:
左偏树(Leftist Tree)是一种可并堆的实现。左偏树是一棵二叉树,它的节点除了和二叉树的节点一样具有键值外,还有一个属性距离(dist)。
距离指的是这个点到某个叶子结点的最短距离
[性质1]节点的左子节点的距离不小于右子节点的距离。
[性质2] 节点的距离等于它的右子节点的距离加1。(显然)
——SD_le
优点:支持堆的logn合并,还可以打标记。
1.模板:
int mer(int x,int y){ if(!x||!y) return x+y; if(val[x]>val[y]) swap(x,y); ch[x][1]=mer(ch[x][1],y); fa[ch[x][1]]=x; if(d[ch[x][0]]<d[ch[x][1]]) swap(ch[x][0],ch[x][1]); d[x]=d[ch[x][1]]+1; return x; }void pop(int x){ int x0=ch[x][0],x1=ch[x][1]; rt=mer(x0,x1); }
所有的操作,都是在mer的支持下进行的。
复杂度证明:
因为每次都是对右子树进行合并,不断走右子树,由于左子树的距离大于等于右子树的距离,所以复杂度logn
例题:
2.Bzoj 2809: [Apio2012]dispatching
n个点组成一棵树,每个点都有一个领导力和费用,可以让一个点当领导,然后在这个点的子树中选择一些费用之和不超过m的点,得到领导的领导力乘选择的点的个数(领导可不被选择)的利润。求利润最大值。
n≤100000 ;
从叶子节点开始,每个点开始是一个大根堆,堆里的就是这个点的人。
往父亲那里合并堆,记录堆的大小,费用的总和。
从儿子合并完毕后,在每个节点,不断踢出费用最大的人,直到费用的总和<=m 这就是这个点的最优方案了。(显然,花费最小的都留下了)
对于每个点,用这个点的领导力乘堆的大小尝试更新答案即可。
注意:和子树合并的时候,rt[x]=mer(rt[x],rt[y]) 注意是rt[y]因为这才是y的所属堆的入口。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=100000+10; int n; ll m; ll c[N],p[N]; int rt[N]; ll siz[N]; ll ans; struct node{ int nxt,to; }e[2*N]; int hd[N],cnt; void add(int x,int y){ e[++cnt].nxt=hd[x]; e[cnt].to=y; hd[x]=cnt; } struct tr{ int ls,rs,d; ll cos; ll sum,siz; }z[N]; void pushup(int x){ z[x].sum=z[z[x].ls].sum+z[z[x].rs].sum+z[x].cos; z[x].siz=z[z[x].ls].siz+z[z[x].rs].siz+1; } int mer(int x,int y){ if(!x||!y) return x+y; if(z[x].cos<z[y].cos) swap(x,y); z[x].rs=mer(z[x].rs,y); if(z[z[x].ls].d<z[z[x].rs].d) swap(z[x].ls,z[x].rs); z[x].d=z[z[x].rs].d+1; pushup(x); return x; } int split(int x){ return mer(z[x].ls,z[x].rs); } void dfs(int x,int fa){ z[x].cos=c[x]; z[x].ls=z[x].rs=0;z[x].siz=1; z[x].sum=c[x]; rt[x]=x; for(int i=hd[x];i;i=e[i].nxt){ int y=e[i].to; if(y==fa) continue; dfs(y,x); rt[x]=mer(rt[x],rt[y]); } while(z[rt[x]].sum>m&&z[rt[x]].siz){ rt[x]=split(rt[x]); } ans=max(ans,z[rt[x]].siz*p[x]); } int main() { scanf("%d%lld",&n,&m); int fa; for(int i=1;i<=n;i++){ scanf("%d%lld%lld",&fa,&c[i],&p[i]); add(fa,i);add(i,fa); } dfs(1,0); printf("%lld",ans); return 0; }
当要用到合并,打标记的堆的时候,就可以考虑左偏树。
以上是关于左偏树——可以标记合并的堆的主要内容,如果未能解决你的问题,请参考以下文章