左偏树——可以标记合并的堆

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;
}

 

当要用到合并,打标记的堆的时候,就可以考虑左偏树。







以上是关于左偏树——可以标记合并的堆的主要内容,如果未能解决你的问题,请参考以下文章

浅谈左偏树在OI中的应用

[模板]左偏树

左偏树(可并堆)

可并堆——左偏树斜堆

并不对劲的左偏树

浅谈左偏树