可并堆--左偏树(Bzoj1367&&Bzoj1455)

来源:互联网 发布:手机追踪软件 编辑:程序博客网 时间:2024/06/07 18:04

今天为了学习可持久化的堆恶补了一下左偏树
左偏树是向左偏的(废话)
所以其实我们也可以写个右偏树QwQ
左偏树除了维护一个键值外还维护了一个距离属性d
我们再定义一个外节点为存在一颗子树为空的节点。
我们定义每个节点的距离属性dis为当前节点到最近一个外子节点的路径长度。
特别地,我们对于一个空节点,他的dis=-1。
有性质:dis[leftson[now]]>=dis[rightson[now]],这保证了左偏性质
那么我们可以计算dis[now]=dis[rightson[now]]+1;
既然是可并堆,合并是少不了的。
假设我们正在维护一个小根堆,那么键值大的往键值小的的堆里插即可。
插完后会发现左偏树的性质可能不满足,此时我们只需要交换左右子树来满足即可。

int merge(int a,int b){    if(a==0||b==0)return a+b;    if(T[a].v<T[b].v)swap(a,b);    T[a].r=merge(T[a].r,b);    if(T[T[a].l].d<T[T[a].r].d)swap(T[a].l,T[a].r);    T[a].d=T[a].r?T[T[a].r].d+1:0;    return a;}

左偏树最重要的操作就是合并,删除堆顶就是合并左右子树
例题:题目链接:BZOJ1367[Baltic2004]sequence
解法在黄源河前辈的论文里有
就是利用可并堆维护中位数,这里用左偏树维护
我们知道一段子序列的最优解一定有一个是中位数
所以我们一段一段地合并中位数并使其符合单调性质
可并堆维护中位数的做法是序列前⌈n/2⌉小的数。
我们用大根堆维护,堆顶即是中位数
由于我们是从大小为1的堆开始合并的所以不用考虑排序,堆自带有序QAQ
但是如果两个堆大小都是奇数的时候我们合并堆后的堆的元素个数会比⌈n/2⌉多1
所以这时候我们要删除堆顶,即直接合并根的左右子树
这样做下去我们会将原数列分成一段段区间,一个区间有一个中位数
代码:

#include<cstdio>#include<vector>#include<cstdlib>#include<cstring>#include<iostream>#include<algorithm>#define LL long longusing namespace std;const int maxn=1000000+10;struct LeftIstTree{    struct treenode{        int v,l,r,d;        treenode(){d=l=r=v=0;}        bool operator<(const treenode&a)const{return v<a.v;}    };    treenode T[maxn];    int merge(int a,int b){        if(a==0||b==0)return a+b;        if(T[a]<T[b])swap(a,b);        T[a].r=merge(T[a].r,b);        if(T[T[a].l].d<T[T[a].r].d)swap(T[a].l,T[a].r);        T[a].d=T[a].r?T[T[a].r].d+1:0;        return a;    }    int root[maxn],m,size[maxn];    void solve(int n){        T[0].d=-1;        for(int i=1;i<=n;i++){            scanf("%d",&T[i].v);            T[i].v-=i; root[++m]=i; size[m]=1;            while(m>1&&T[root[m]].v<T[root[m-1]].v){                int k=size[m]&size[m-1]&1;                root[m-1]==merge(root[m-1],root[m]);                if(k)root[m-1]=merge(T[root[m-1]].l,T[root[m-1]].r);                size[m-1]+=size[m]; m--;            }        }        int cur=1; LL ans=0;        for(int i=1;i<=m;i++)            for(int j=1;j<=size[i];j++,cur++)ans+=abs(T[root[i]].v-T[cur].v);        printf("%lld\n",ans);    }}Left_Ist_Tree;int main(){    int n; scanf("%d",&n);    Left_Ist_Tree.solve(n);}

例题:Bzoj1455: 罗马游戏
左偏树裸题,注意杀人的时候不要把被杀的人扔出堆

#include<cstdio>#include<vector>#include<cstdlib>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int maxn=2000000+10;int n,m,f[maxn],a[maxn];bool Died[maxn];int find(int x){    return x==f[x]?x:f[x]=find(f[x]);}struct Node{    int d,v,l,r;    Node(){d=l=r=v=0;}}T[maxn];int merge(int a,int b){    if (!a||!b) return a+b;    if (T[a].v>T[b].v) swap(a,b);    T[a].r=merge(T[a].r,b);    if (T[T[a].l].d<T[T[a].r].d) swap(T[a].l,T[a].r);    T[a].d=T[a].r?T[T[a].r].d+1:0;    return a;}int main(){    scanf("%d",&n);    for (int i=1;i<=n;++i)        scanf("%d",&a[i]),f[i]=i,T[i].v=a[i];    scanf("%d",&m);    T[0].d=-1; int x,y;    char str[10];    for (int i=1;i<=m;++i){        scanf("%s",str);        if (str[0]=='M'){            scanf("%d%d",&x,&y);            if (Died[x]||Died[y]) continue;            int p=find(x),q=find(y);            if (p!=q){                int t=merge(p,q);                f[p]=t; f[q]=t;            }        }else{            scanf("%d",&x);int p=find(x);            if (Died[x]){printf("0\n");continue;}            printf("%d\n",T[p].v); Died[p]=1;            int t=merge(T[p].l,T[p].r);            f[p]=t; f[t]=t;        }    }}
3 1
原创粉丝点击