splay反转-P3391 文艺平衡树

来源:互联网 发布:linux下重启的命令 编辑:程序博客网 时间:2024/05/29 17:20

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; }
2 4
原创粉丝点击