bzoj 3223 文艺平衡树 Splay详细解析

本文不会过于深入介绍splay的证明,感性认知- -。
对于树我们知道它有两个旋转,对于一个节点,如果它是左儿子,那么它只能右旋,如果它是右儿子,它只能左旋,在splay的时候,我们的目的是把一个节点x转到一个根上,我们考虑两种情况,
1在三点一线的时候(3个点没有弯曲)那么先旋转x的父亲,在旋转x,除此之外都直接旋转x就好了,这样是为了让树更平衡。(画个图感性认知一下0 0)

splay大概流程

void rotate(int x,int &k){
    int y=t[x].fa,z=t[y].fa,p;
    if(t[y].ch[0]==x)p=1;else p=0;
    if(y==k)k=x;
    else {if(t[z].ch[0]==y)t[z].ch[0]=x;else t[z].ch[1]=x;}
    t[x].fa=z;t[y].fa=x;t[y].ch[p^1]=t[x].ch[p];
    t[t[x].ch[p]].fa=y;t[x].ch[p]=y;
    update(x);update(y);
}
void splay(int x,int &k){
    while(x!=k){
        int y=t[x].fa,z=t[y].fa;
        if(y!=k){
            if(t[y].ch[0]==x^t[z].ch[0]==y)rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}

然后我们来分析这道题
对于本题来说,我们需要翻转区间。首先我们考虑在数组上,每一个位置对应一个值,如果翻转区间[ L, R ] 就是把所有大于l-1 并且小于r+1 的位置翻转,那么我们很自然的想到bst的性质,然后想到平衡树(根据题目名称可知)。
然后把翻转转到树上就是交换节点的左右子树,然后递归下去交换。
接下来就比较绕了,为什么交换左右子树能保持bst性质呢?如何找到树上某个点呢?

因为我们要找的是在数组上的位置,所以应该是第几个点,而不是第几号点,所以只有在最开始的时候节点编号才恰好为第几个点。那么其实在每次交换子树的时候,我们其实就是在原数组中该位置的点左边部分和右边部分给交换了位置。所以能解释它为什么两边siz都变了还能满足bst性质。

更可以这么理解的是,每次交换后,都相当于改变了一次每个点是第几号点。
例如1,2,3,4,5我们交换了2到4,变为1,4,3,2,5我们可以发现4它从第4个点变成了第2个点。自动就改变了,是不是很神奇O(∩_∩)O哈哈~

下面是我A掉的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
using namespace std;
int n,m,rt;
struct date{
    int ch[2],fa,lazy,siz;
    date(){ch[0]=-1;ch[1]=-1;}
};
date t[N];
void update(int x){
    int l=t[x].ch[0],r=t[x].ch[1];
    t[x].siz=t[l].siz+t[r].siz+1;
}
void pushdown(int k){
    if(t[k].lazy){
        int &l=t[k].ch[0],&r=t[k].ch[1];
        swap(l,r);
        t[l].lazy^=1,t[r].lazy^=1,t[k].lazy^=1;
    }
}
void rotate(int x,int &k){
    int y=t[x].fa,z=t[y].fa,p;
    if(t[y].ch[0]==x)p=1;else p=0;
    if(y==k)k=x;
    else {if(t[z].ch[0]==y)t[z].ch[0]=x;else t[z].ch[1]=x;}
    t[x].fa=z;t[y].fa=x;t[y].ch[p^1]=t[x].ch[p];
    t[t[x].ch[p]].fa=y;t[x].ch[p]=y;
    update(x);update(y);
}
void splay(int x,int &k){
    while(x!=k){
        int y=t[x].fa,z=t[y].fa;
        if(y!=k){
            if(t[y].ch[0]==x^t[z].ch[0]==y)rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}
int build(int l,int r,int f){
    if(l>r)return -1;
    if(l==r){
        t[l].fa=f,t[l].siz=1,t[l].lazy=0;
        return l;
    }
    else{
        int mid=(l+r)>>1;
        t[mid].ch[0]=build(l,mid-1,mid);
        t[mid].ch[1]=build(mid+1,r,mid);
        t[mid].fa=f,t[mid].lazy=0;
        update(mid);
        return mid;
    }
}
int find(int k,int val){
    pushdown(k);
    int l=t[k].ch[0],r=t[k].ch[1];
    if(t[l].siz+1==val)return k;
    else if(t[l].siz>=val)return find(l,val);
    else return find(r,val-t[l].siz-1);
}
void rev(int l,int r){
    int x=find(rt,l),y=find(rt,r+2);
    splay(x,rt);splay(y,t[x].ch[1]);
    int z=t[y].ch[0];
    t[z].lazy^=1;
}
int main(){
    int l,r;
    scanf("%d%d",&n,&m);
    rt=build(0,n+1,-1);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&l,&r);
        rev(l,r);
    }
    for(int i=1;i<=n;i++)
    printf("%d ",find(rt,i+1));
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值