斜堆

斜堆是可并堆的一种,与左偏树类似,只不过没有零距离的概念,在进行合并时不需要比较左右儿子的dis,直接进行合并即可。

这里的父指针并不是真的维护父亲,而是对于并查集的找根操作,因为只要求删除最小值,所以只需要知道一个集合的根节点即可,于是可以路径压缩。

struct SkewHeap{
    struct tree{
        int l,r,v,f;
    }t[N];
    #define l(p) (t[p].l)
    #define r(p) (t[p].r)
    #define v(p) (t[p].v)
    #define f(p) (t[p].f)
    int tot;
    int find(int x){/*路径压缩并查集*/
        return x==f(x)?x:f(x)=find(f(x));
    }
    inline int top(int x){/*返回x所在堆的最小值*/
        if(v(x)==-1)return -1;
        return v(find(x));
    }
    inline int pop(int p){/*返回并删除p所在堆的最小值*/
        if(v(p)==-1)return -1;
        p=find(p);
        int x=v(p);
        f(l(p))=f(r(p))=f(p)=merge(l(p),r(p));/*路径压缩的需要,三者均指向合并后的节点*/
        l(p)=r(p)=0;/*清空儿子*/
        v(p)=-1;/*删除标记*/
        return x;
    }
    int merge(int x,int y){/*合并x和y两个堆*/
        if(!x||!y)return x|y;
        if(v(x)>v(y)||(v(x)==v(y)&&x>y))swap(x,y);
        r(x)=merge(r(x),y);/*合并x的右子树和y进行并赋给x*/
        swap(l(x),r(x));/*直接进行交换*/
        return x;
    }
    inline void mergeset(int x,int y){/*合并x和y所在的堆*/
        if(v(x)==-1||v(y)==-1)return;
        x=find(x),y=find(y);
        if(x==y)return;
        f(x)=f(y)=merge(f(x),f(y));
    }
    inline void create(int x){/*创建一个初始只含x的堆*/
        t[++tot]={0,0,x,tot};
    }
    inline void push(int p,int x){/*在p所在的小根堆中插入x*/
        t[++tot]={0,0,x,tot};/*为x新建一个堆*/
        p=find(p);
        f(p)=f(tot)=merge(p,tot);
    }
    inline int modify(int p,int v){/*修改p所在堆的根节点的权值*/
        p=find(p);/*找到根节点*/
        v(p)+=v;/*修改权值*/
        int x=merge(l(p),r(p));/*合并子树为新根x*/
        l(p)=r(p)=0;/*孤立旧根*/
        f(p)=f(x)=merge(p,x);/*合并新旧根节点为总根*/
        return f(p);/*返回合并后的总根*/
    }
}sh;

可持久化斜堆,与可持久化左偏树类似。

int merge(int x,int y){
    if(!x||!y)return x|y;
    if(v(x)>v(y))swap(x,y);
    int p=++tot;
    t[p]=t[x];
    r(p)=merge(r(p),y);
    swap(l(p),r(p));
    d(p)=d(r(p))+1;
    return p;
}

斜堆也可以随机合并,随机决定是否交换儿子。

int merge(int x,int y){
    if(!x||!y)return x|y;
    if(v(x)>v(y))swap(x,y);
    r(x)=merge(r(x),y);
    if(rand()&1)swap(l(x),r(x));
    return x;
}

维护多个大根堆,支持一种操作,将两个大根堆的根节点减少为原来的一半,之后合并两个大根堆并返回合并后的最大值。对于修改根节点的操作,可以先修改权值,之后取出根节点,再重新把根节点加入原来的堆中。

    inline int modify(int p){
        p=find(p);/*找到根节点*/
        v(p)>>=1;/*修改权值*/
        int x=merge(l(p),r(p));/*合并子树为新根x*/
        l(p)=r(p)=0;/*孤立旧根节点*/
        return f(p)=f(x)=merge(p,x);/*合并旧根与新根为总根*/
    }
    inline int solve(int x,int y){
        x=find(x),y=find(y);
        if(x==y)return -1;
        x=modify(x),y=modify(y);
        f(x)=f(y)=merge(x,y);/*合并x与y*/
        return v(f(x));
    }

一棵有根树,每个点有领导力与费用,选一个点当领导,之后在这个点的子树中选择费用之和不超过m的点,利润为领导的领导力*选择的点的个数,领导可不被选择,求利润最大值。维护多个大根堆,从叶子节点往父亲那里合并,所有儿子合并完后,不断删除费用最大的人,直到费用总和<=m,即为该点的最优方案。

    inline void pushup(int p){
        sm(p)=sm(l(p))+sm(r(p))+v(p);
        sz(p)=sz(l(p))+sz(r(p))+1;
    }
    inline int split(int p){
        return merge(l(p),r(p));
    }
    int merge(int x,int y){
        if(!x||!y)return x|y;
        if(v(x)<v(y))swap(x,y);
        r(x)=merge(r(x),y);
        swap(l(x),r(x));
        pushup(x);
        return x;
    }
    void dfs(int x){
        v(x)=sm(x)=cost[x];
        l(x)=r(x)=0;
        sz(x)=1;
        rt[x]=x;
        for(auto y:v[x]){
            dfs(y);
            rt[x]=merge(rt[x],rt[y]);
        }
        while(sm(rt[x])>m)rt[x]=split(rt[x]);
        ans=max(ans,sz(rt[x])*lead[x]);
    }

n个城池组成一课有根树,i收fi管辖。有m个骑士,初始战斗力为si,第一个攻击的城池为ci,每个城池有一个防御力hi,若骑士战斗力>=hi则可以占领,否则会牺牲,占领后骑士战斗力会发生变化,继续攻击fi,知道1号城池或牺牲,对于没错城池,若ai=0,攻占后战斗力增加vi,若ai=1,攻占后战斗力乘以vi,每个骑士单独计算,问每个城市有多少个骑士在这里牺牲,每个骑士攻占多少个城池。讲同出生城池的骑士合并为一堆,从下到上遍历城池树,一直用当前骑士团最弱的和hi进行比较,死的弹出,没死的放到父节点并更改战斗力。左偏树维护最小值和标记下放,注意要先乘法下放再加法下放。

    inline void add(int p,int x){
        v(p)+=x;
        a(p)+=x;
    }
    inline void mul(int p,int x){
        v(p)*=x;
        a(p)*=x;
        m(p)*=x;
    }
    inline void pushdown(int p){
        mul(l(p),m(p));
        add(l(p),a(p));
        mul(r(p),m(p));
        add(r(p),a(p));
        a(p)=0;
        m(p)=1;
    }
    int merge(int x,int y){
        if(!x||!y)return x|y;
        pushdown(x),pushdown(y);
        if(v(x)>v(y))swap(x,y);
        r(x)=merge(r(x),y);
        swap(l(x),r(x));
        return x;
    }
    inline int split(int p){
        pushdown(p);
        return merge(l(p),r(p));
    }
    void dfs(int x){
        for(int i=g.h[x];i;i=g.e[i].next){
            int y=g.e[i].to;
            dep[y]=dep[x]+1;
            dfs(y);
        }
        while(rt[x]&&v(rt[x])<h[x]){
            a1[x]++;
            a2[rt[x]]=dep[c[rt[x]]]-dep[x];
            rt[x]=split(rt[x]);
        }
        if(a[x])mul(rt[x],v[x]);
        else add(rt[x],v[x]);
        if(x>1)rt[fa[x]]=merge(rt[x],rt[fa[x]]);
        else while(rt[x]){
            a2[rt[x]]=dep[c[rt[x]]]+1;
            rt[x]=split(rt[x]);
        }
    }
    for(int i=1;i<=m;i++){
        int x;
        cin>>x>>c[i];
        lt.create(x);
        lt.rt[c[i]]=lt.merge(i,lt.rt[c[i]]);
    }

给定一个序列a,求一个递增序列b,使得sum(i=1->n)(|a[i]-b[i]|)最小。若a是一个单调不降的序列,那么可以取b[i]=a[i],若a单调不升,那么取b[i]的a的中位数为最优,于是将序列分成一些单调不升的几段,每段都取中位数,但是这些中位数组成的序列可能并不是单调不降的,所以需要合并两个区间并重新取中位数。对于求区间中位数,就是所有元素入堆,病弹出最大值知道只剩一半元素。合并区间的前提是该区间中位数比后面区间中位数大。对于每个ai,减去i并不会影响,于是可以转化成单调不降的b数列。

struct node{
    int l,r,sz,rt,v;
}s[N];
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        lt.create(x-i);
    }
    for(int i=1;i<=n;i++){
        s[++top]={i,i,1,i,lt.t[i].v};
        while(top>1&&s[top-1].v>s[top].v){
            top--;
            s[top].rt=lt.merge(s[top].rt,s[top+1].rt);
            s[top].sz+=s[top+1].sz;
            s[top].r=s[top+1].r;
            while(s[top].sz>(s[top].r-s[top].l+2)/2){
                s[top].sz--;
                s[top].rt=lt.split(s[top].rt);
            }
            s[top].v=lt.t[s[top].rt].v;
        }
    }
    for(int i=1,p=1;i<=n;i++){
        p+=(i>s[p].r);
        ans+=abs(s[p].v-lt.t[i].v);
    }
    cout<<ans<<'\n';
    for(int i=1,p=1;i<=n;i++){
        p+=(i>s[p].r);
        cout<<s[p].v+i<<' ';
    }

支持三中操作,将某个点的权值减小到0,将某个集合最大值减小到某个值,合并两个集合。左偏树维护大根堆,父指针记录根节点,并不需要维护父亲,对于修改一个点的点权,先独立该节点,合并该节点的子树,之后将该节点重新加入堆中。

    inline void extract(int p){
        int x=merge(l(p),r(p));
        l(p)=r(p)=0;
        f(l(p))=f(r(p))=f(p)=f(x)=f(find(p))=merge(x,find(p));
    }
        while(m--){
            int op,a,b;
            cin>>op>>a;
            if(op!=2)cin>>b;
            if(op==2){
                lt.t[a].v=0;
                lt.extract(a);
            }
            else if(op==3){
                a=lt.find(a);
                lt.t[a].v-=lt.t[a].v>b?b:lt.t[a].v;
                lt.extract(a);
            }
            else if(op==4)lt.mergeset(a,b);
        }
posted @ 2022-11-14 18:03  半步蒟蒻  阅读(55)  评论(0)    收藏  举报