配对堆

配对堆是可并堆的一种,支持插入、查询最小值、删除最小值、修改元素,但是无法可持久化。

配对堆是满足堆性质的带权多叉树,使用儿子-兄弟表示法存储。

在删除最小值时,为了保证总的均摊复杂度,需要把儿子们两两配对,再用merge把配成一对的两个儿子合并到一起,再将新产生的堆从右往左挨个合并。

这里的父指针只是并查集的父指针,由于操作中只有删除根节点,所以只需要知道根节点,使用路径压缩。

struct PairingHeap{
    struct tree{
        int v,c,s,f;
    }t[N];
    #define v(p) (t[p].v)
    #define c(p) (t[p].c)/*指向该节点的第一个儿子*/
    #define s(p) (t[p].s)/*sibling指向节点的下一个兄弟*/
    #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),y=merge(c(p));/*合并所有儿子产生新根*/
        f(p)=f(y)=y;/*并查集指向新根*/
        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);
        s(y)=c(x);/*y的兄弟链增添一个x的儿子*/
        c(x)=y;/*y成为x的儿子*/
        return x;
    }
    int merge(int p){/*合并p节点的所有兄弟*/
        if(!p||!s(p))return p;
        int x=s(p)/*p的下一个兄弟*/,y=s(x);/*再下一个兄弟*/
        s(p)=s(x)=0;/*拆散*/
        return merge(merge(y),merge(p,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(x,y);
    }
    inline void create(int x){/*新建一个只含x的堆*/
        t[++tot]={x,0,0,tot};
    }
    inline void push(int p,int x){/*在p所在的堆中插入x*/
        t[++tot]={x,0,0,tot};
        p=find(p);
        f(p)=f(tot)=merge(p,tot);
    }
}ph;

配对堆可以减小一个元素的值,这里需要维护它的父指针。

struct PairingHeap{
    struct tree{
        int c,s,v,f;
    }t[N];
    #define c(p) (t[p].c)
    #define s(p) (t[p].s)
    #define v(p) (t[p].v)
    #define f(p) (t[p].f)
    int find(int x){
        return x==f(x)?x:find(f(x));
    }
    int merge(int x,int y){
        if(!x||!y)return x|y;
        if(v(x)>v(y))swap(x,y);
        if(c(x))f(c(x))=y;
        s(y)=c(x);
        f(y)=x;
        c(x)=y;
        return x;
    }
    int merge(int p){
        if(!p)return p;
        f(p)=0;
        if(!s(p))return p;
        int x=s(p),y=s(x);
        f(x)=s(p)=s(x)=0;
        return merge(merge(y),merge(p,x));
    }
    int decrease(int x,int v){
        int p=find(x);
        v(x)=v;
        if(x==p)return x;
        if(c(f(x))==x)c(f(x))=s(x);
        else s(f(x))=s(x);
        if(s(x))f(s(x))=f(x);
        s(x)=f(x)=0;
        return merge(p,x);
    }
};

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

    inline int modify(int p){
        p=find(p);/*找到根节点*/
        v(p)>>=1;/*修改权值*/
        int x=merge(c(p));/*合并产生新根*/
        c(p)=s(p)=0;/*孤立旧根*/
        f(p)=f(x)=merge(p,x);/*合并新旧根为总根*/
        return f(p);
    }
    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,即为该点的最优方案。注意对多叉树的pushup.

    inline void pushup(int p){
        sm(p)+=sm(c(p));
        sz(p)+=sz(c(p));
    }
    inline int split(int p){
        return merge(c(p));
    }
    int merge(int x,int y){
        if(!x||!y)return x|y;
        if(v(x)<v(y))swap(x,y);
        s(y)=c(x);
        c(x)=y;
        pushup(x);
        return x;
    }
    void dfs(int x){
        v(x)=sm(x)=cost[x];
        c(x)=s(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]);
    }
}lt;

给定一个序列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<<' ';
    }
posted @ 2022-11-14 18:03  半步蒟蒻  阅读(220)  评论(0)    收藏  举报