zoukankan      html  css  js  c++  java
  • 左偏树——可以标记合并的堆

    左偏树:

    左偏树(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;
    }

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

  • 相关阅读:
    reverse的一些做题教训&&trick
    reverse的一些常用资料
    [攻防世界
    JavaScript 常用函数 通用函数
    宝塔突然出现Internal Server Error
    centos7上用nginx部署前后端分离项目
    postgres 如何把多行数据,合并一行,返回json字符串
    文艺平衡树
    FHQ Treap 板子
    Java后台开发有哪些提升效率的插件?
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9368414.html
Copyright © 2011-2022 走看看