zoukankan      html  css  js  c++  java
  • 文艺平衡树(Splay)题解

    题目描述:

    您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 。

    输入:

    第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n)  m表示翻转操作次数
    接下来m行每行两个数[l,r] 数据保证 1<=l<=r<=n 

    输出:

    输出一行n个数字,表示原始序列经过m次变换后的结果 

    题目地址:

    bzoj洛谷

    题解:

    这题目调了我好久,最后发现是全局变量的锅qwq。

    最近刚学Splay,总算搞明白了Splay维护序列的原理。就来发题解记录一下。

    首先我们的这一颗Splay的中序遍历即为我们要求的结果。这里的Splay并不是传统意义上的平衡二叉树(即某个节点的左儿子的权值小于该节点的权值,右儿子节点的权值大于该节点的权值)节点权值在这里除了最后输出是没有意义的。我们其实是用一个数在Splay中的排名来代表原序列上这个排名的所在位置的(注意是所在位置,并不代表某一特定值)。

    然后我们考虑如何找出区间$[l,r]$,我们首先将排名为$l-1$的节点一路旋转到根,然后将排名为$r+1$的节点旋转为根节点的右儿子(此时它一定在根节点右子树中,因为排名比根节点的排名$l-1$更大)。然后此时根节点的右儿子的左子树内就是我们要求的区间$[l,r]$的位置了(因为这里面的数都是排名大于$l-1$且小于$r+1$的,可以考虑二叉查找树的性质)。然后将左右子树互换就好了(也是二叉查找树的性质),注意交换后节点的权值变了,但是所代表的位置是不变的。

    最后用两个哨兵$(0$与$n+1)$什么的来防止找不到$l-1$与$r+1$,建树无所谓,像线段树那样建没关系。然后在加上延迟标记,等要访问这个节点的时候再交换左右子节点。

    附上代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int inf=1<<30;
    const int N=100005;
    int w[N],f[N],size[N],value[N],tag[N],tr[N][2];
    int n,m,sz,root;
    
    int read(){
        int x=0;
        char ch=getchar();
        while(ch<'0' || ch>'9') ch=getchar();
        while(ch>='0' && ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
        return x;
    }
    
    int get(int x){return tr[f[x]][1]==x;}
    void updata(int x){if(x) size[x]=size[tr[x][0]]+size[tr[x][1]]+1;}
    
    void pushdown(int x){
        if(x && tag[x]){
            tag[tr[x][0]]^=1;
            tag[tr[x][1]]^=1;
            swap(tr[x][0],tr[x][1]);
            tag[x]=0;
        }
    }
    
    void rotate(int x){//正常旋转函数
        int fa=f[x],gfa=f[fa];
        int now=get(x);
        tr[fa][now]=tr[x][now^1],f[tr[fa][now]]=fa;
        tr[x][now^1]=fa,f[fa]=x;
        f[x]=gfa;
        if(gfa) tr[gfa][tr[gfa][1]==fa]=x;
        updata(x),updata(fa);
    }
    
    void Splay(int x,int goal){//Splay加了目标
        for(int fa;(fa=f[x])!=goal;rotate(x)){
            if(f[fa]!=goal){
                rotate(get(fa)==get(x) ? fa : x);
            }
        }
        if(!goal) root=x;
    }
    
    int build(int l,int r,int fa){//建树
        if(l>r) return 0;
        int mid=(l+r)>>1;
        int now=++sz;
        f[now]=fa;
        value[now]=w[mid];
        tr[now][0]=build(l,mid-1,now);
        tr[now][1]=build(mid+1,r,now);
        updata(now);
        return now;
    }
    
    int find(int x){//查找排名函数
        int now=root;
        while(1){
            pushdown(now);
            if(x<=size[tr[now][0]]) now=tr[now][0];
            else{
                x-=size[tr[now][0]]+1;
                if(!x) return now;
                now=tr[now][1];
            } 
        }
    }
    
    void reserve1(int l,int r){
        l=find(l),r=find(r);//先找排名
        Splay(l,0),Splay(r,l);//把l转移到根节点,r转移到l的右儿子
        tag[tr[tr[root][1]][0]]^=1;//打上标记
    }
    
    void dfs(int x){//最后中序遍历输出答案
        pushdown(x);
        if(tr[x][0]) dfs(tr[x][0]);
        if(value[x]!=inf && value[x]!=-inf){
            printf("%d ",value[x]);
        }
        if(tr[x][1]) dfs(tr[x][1]);
    }
    
    int main(){
        n=read(),m=read();
        w[0]=-inf,w[n+1]=inf;
        for(int i=1;i<=n;++i) w[i]=i;
        root=build(0,n+1,0);//建树
        for(int i=1,l,r;i<=m;++i){
            l=read(),r=read();
            reserve1(l,r+2);//因为用了哨兵所以并不是我说的$l-1$与$r+1$
        }
        dfs(root);
        return 0;
    }
  • 相关阅读:
    P2045 方格取数加强版
    P2774 方格取数问题
    日记——OI历程
    6.30考试
    6.29考试
    数论...
    6.28数论测试
    洛谷P3802 小魔女帕琪
    hosts
    博客设置
  • 原文地址:https://www.cnblogs.com/Asika3912333/p/11767945.html
Copyright © 2011-2022 走看看