splay反转-P3391 文艺平衡树

https://daniu.luogu.org/problem/show?pid=3391
首先你要理解splay的旋转;
其实反转和treap是一样的,都是二叉树的旋转;
但是treap用随机数来维护树高,而slplay用双旋来维护;
那我们怎么翻转这个区间呢?
对于l~r
我们先把l-1旋转到根节点
再把r+1旋转到更节点的右儿子;
显然,这样树根的右子节的左子节点点是l~r;
那么我们只要把这个区间打一个标记;
一旦我们要访问这个节点,先交换左右子树,再把标记下穿;
如果子树已经有标记就抵消;
这样就实现了翻转区间;
然后我们为l-1,r+1不越界,所以我们把整个区间的范围变化成0~n+2;
这样的话要注意一些细节问题;
有些心得说说;
在这个树里,树的权值(即节点)是一开始的下标;
但是我们维护的时这些下标的顺序;
因为顺序的变化,所以我们要把节点与节点之间的关系换掉;
但是节点本身不变;
这个就是和以前学的线段树什么的不一样的地方;
所以我们在写这类二叉搜索树的时候,一定要搞清楚。
我们维护的顺序是什么,节点的权值是什么;

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int c[100005][2],fa[100005],size[100005],rev[100005];//c数组记录左右儿子,rev就是标记; 
int n,m,x,y,z,rt;
void pushup(int x){size[x]=size[c[x][0]]+size[c[x][1]]+1;}
void pushdown(int x){
    if(!rev[x])return;
    swap(c[x][0],c[x][1]);
    rev[c[x][0]]^=1;
    rev[c[x][1]]^=1;
    rev[x]=0;
}
void make(int l,int r,int z){
    if(l>r)return;
    if(l==r){
        fa[l]=z;
        size[l]=1;
        if(l<z)c[z][0]=l;else c[z][1]=l;
        return;
    }
    int mid=l+r>>1;
    make(l,mid-1,mid);
    make(mid+1,r,mid);
    fa[mid]=z;
    pushup(mid);
    if(mid<z)c[z][0]=mid;else c[z][1]=mid;
}
int find(int x,int k){
    pushdown(x);
    if(size[c[x][0]]+1==k)return x;
    if(size[c[x][0]]+1>k)return find(c[x][0],k);
    return find(c[x][1],k-size[c[x][0]]-1);
}
void turn(int x,int &k){
    int y=fa[x],z=fa[y],l,r;
    if(c[y][0]==x)l=0;else l=1;
    r=l^1;//这里直接用l,r表示节点,这就是c[][0/1]的妙用,思想类似与滚存; 
    if(y==k)k=x;else
    if(c[z][0]==y)c[z][0]=x;else c[z][1]=x;
    fa[x]=z;fa[y]=x;fa[c[x][r]]=y;
    c[y][l]=c[x][r];
    c[x][r]=y;
    pushup(y); pushup(x);
}
void splay(int x,int &k){
    while(x!=k){//为什么这里x不用变,因为我们把x和其他点的关系改掉了,本身的x是不用边的; 
                //但是k最终会变成x; 
        int y=fa[x],z=fa[y];
        if(y!=k)//如果y不是k,直接转两次,不然转一次; 
            if(c[y][0]==x^c[z][0]==y)turn(x,k);
            else turn(y,k);
        turn(x,k);
    }
}
void rever(int l,int r){
    int x=find(rt,l),y=find(rt,r);
    splay(x,rt);
    splay(y,c[rt][1]);
    rev[c[y][0]]^=1;//用异或去添加,抵消标记 
}
int main()
{
    scanf("%d%d",&n,&m);
    make(1,n+2,0);rt=(n+3)>>1;//建造一个0~n+2的树,节点个数是n+3个; 
    while(m--){
        scanf("%d%d",&x,&y);
        x++;y++;
        rever(x-1,y+1);
    }
    for(int i=2;i<=n+1;i++)printf("%d ",find(rt,i)-1);
    //因为我们一开始建立的0~n+2的区间,用了n+3个位子,第一个位子的下标是1,但是我们第一个数是0而不是1,所以-1; 
}
posted @ 2017-03-03 11:12  largecube233  阅读(143)  评论(0编辑  收藏  举报