[HNOI2017]单旋splay

题目大意

求用 spaly 维护 m 个操作,每次操作的节点的深度。
操作分为 5 种: (1) 插入一个关键字为 key 的节点; (2) 单旋最小值到根; (3) 单旋最大值到根; (4) 执行操作 (2) 并删除最小值; (5) 执行操作 (3) 并删除最大值。

数据范围

1m105,1key109 .
保证出现的每个 key 值都不和前面存在的重复,保证操作合法。

解题报告

以单旋最小值为例说明单旋的一些性质:

最小值 x spaly 中单旋到根的过程中一定都是作为左儿子,并且 x 的左儿子一定始终为空,单旋的过程中不断的将 x 的右子树给自己的父亲节点(设为 y ) 替代自己原来的位置,然后 x 成为 y 的父亲, y 变成 x 的右儿子,当 x 成为根时,整棵树的形态几乎不变作为 x 的右子树,x 原来的位置由 x 原来的右子树代替。

此时,整棵树深度的变化如下, x 的深度变为 1 x 原来的右子树深度不变,其它节点的深度都增加了 1 .

单旋最大值的情况与此类似, x 的深度变为 1 x 原来的左子树深度不变,其它节点的深度都增加了 1 .

注意到,这棵树的 dfs 入栈序就是 key 值的升序 应该订正为:高度改变的节点的 key 值为连续的一段区间,因此利用上述性质维护树的形态和任意一种能维护序列区间的数据结构维护高度变化,就可以有效率的模拟单旋的操作。

剩下的问题就只是如何维护插入的操作。

若插入时树不为空,则插入后,新节点 x 要么是它在树中前驱 prev 的右儿子,要么是它在树中后继 next 的左儿子.

若两者不同时存在,那么问题就好办了;

对于两者同时存在的情况, prev next 一定存在祖先关系(否则设 y=lca(prev,next) ,则 prev<y<next ,与前驱和后继的定义矛盾)

比较 prev next 的深度判断谁是祖先,若 prev 是祖先,则 prev 存在右儿子, next 不存在左儿子(一样是反证法可证);若 next 是祖先,则 next 存在左儿子, prev 不存在右儿子。

因此,可以使用两个树状数组,第一个用来求前驱后继和最大最小值,第二个用来维护深度的增量,就可以解决这道题了。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

int gint(){
    char c; int f=1;
    while(c=getchar(),c<48||c>57)
        if(c=='-')f=-1;
    int x=0;
    for(;c>47&&c<58;c=getchar()){
        x=x*10+c-48;
    }
    return x*f;
}

#define max_N 100005

int n,m,a[max_N],b[max_N],h[max_N];

int bit1[max_N],bit2[max_N];

inline void add(int*bit,int x,int y){
    for(int i=x;i&&i<=n;i+=i&-i)bit[i]+=y;
}

inline int sum(int*bit,int x){
    int res=0;
    for(int i=x;i;i-=i&-i)res+=bit[i];
    return res;
}

inline int k_th(int k){
    int ans=0;
    for(int i=17;~i;--i){
        ans^=1<<i;
        if(ans>=n||bit1[ans]>=k)ans^=1<<i;
        else k-=bit1[ans];
    }
    return ans+1;
}

inline int hash(int x){
    return lower_bound(h+1,h+1+n,x)-h;
}

int rt,sz,p[max_N],ch[2][max_N],dep[max_N];

int main(){
    m=gint();
    for(int i=1;i<=m;++i){
        a[i]=gint();
        if(a[i]==1)h[++n]=b[i]=gint();
    }
    sort(h+1,h+1+n);
    for(int i=1;i<=m;++i){
        if(a[i]==1){
            int x=hash(b[i]);
            if(!rt)rt=x;
            else{
                int rk,pre=0,nex=0;
                rk=sum(bit1,x)+1;
                if(rk<=sz)nex=k_th(rk);
                rk=sum(bit1,x-1);
                if(rk)pre=k_th(rk);
                if(!pre||dep[pre]+sum(bit2,pre)<dep[nex]+sum(bit2,nex)){
                    p[x]=nex;
                    ch[0][nex]=x;
                }
                else{
                    p[x]=pre;
                    ch[1][pre]=x;
                }
            }
            dep[x]=sum(bit2,p[x])+dep[p[x]]+1;
            int tmp=sum(bit2,x);
            add(bit2,x,-tmp),add(bit2,x+1,tmp);
            add(bit1,x,1),++sz;
            printf("%d\n",dep[x]);
        }
        else if(a[i]==2||a[i]==4){
            int x=k_th(1),tmp=sum(bit2,x);
            printf("%d\n",dep[x]+tmp);
            if(x!=rt){
                dep[x]=1-tmp;
                add(bit2,p[x],1);
                if(ch[1][x])ch[0][p[x]]=ch[1][x],p[ch[1][x]]=p[x];
                ch[1][x]=rt,p[rt]=x;
                p[x]=0,rt=x;
            }
            if(a[i]==4){
                add(bit1,x,-1),--sz;
                rt=ch[1][x],p[rt]=0;
                add(bit2,1,-1);
            }
        }
        else{
            int x=k_th(sz),tmp=sum(bit2,x);
            printf("%d\n",dep[x]+tmp);
            if(x!=rt){
                dep[x]=1-tmp;
                add(bit2,1,1),add(bit2,p[x]+1,-1);
                if(ch[0][x])ch[1][p[x]]=ch[0][x],p[ch[0][x]]=p[x];
                ch[0][x]=rt,p[rt]=x;
                p[x]=0,rt=x;
            }
            if(a[i]==5){
                add(bit1,x,-1),--sz;
                rt=ch[0][x],p[rt]=0;
                add(bit2,1,-1);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值