zoukankan      html  css  js  c++  java
  • [平衡树-Splay]文艺平衡树_区间翻转

    题意

    给你一个1-n的排列,1,2,...n

    求翻转k次之后的序列

    例如:1,2,3,5,4 翻转2-4 -> 1,5,3,2,4

    题解

    • 首先,splay操作之后的中序遍历是不会发生变化的。初始序列无论splay多少次中序遍历不变
    • 中序遍历有一个显而易见的性质,就是左子树和右子树交换之后,中序遍历也就翻转了。
    • 题目中的翻转操作也可以通过某个点的左右子树交换
    • 问题1:这个点的子树要锁定题目要求的区间里,即这个点的的子树要覆盖这个区间,这样才能通过左右子树交换解决问题
    • 我们可以通过splay操作让这个点的子树覆盖区间,然后交换。即将l-1的变成root,r+1变成root的右儿子,这样就保证了r+1的左儿子一定是区间[l,r]
    • 问题2:怎么确定第l/r个数
    • 可以直接通过比较size来确定下标,这是因为该splay树中序遍历之后就是所得序列。
    • 优化:我们可以发现存在一个点的子树翻转多次的情况,可以用tag标记一下

    代码

    #pragma GCC optimize(2)
    #pragma GCC optimize(3,"Ofast","inline")
    #include<bits/stdc++.h>
    using namespace std;
    const int N = 1e5+7;
    const int inf = 0x3f3f3f;
    #define Mid (l+r>>1)
    int rt;//根节点
    int tot;//节点个数
    struct node {
        int fa;//父亲节点
        int ch[2];//子节点
        int val;//权值
        int tag;//标记
        int sz;//子树大小
    }s[N];
    int arr[N];
    
    struct Splay {
    
        //在改变节点位置后,将节点x的size更新
        inline void maintain(int x) {
            s[x].sz = s[s[x].ch[0]].sz+s[s[x].ch[1]].sz + 1;
        }
    
        //判断该节点是左儿子还是右儿子
        inline bool get(int x) {return x == s[s[x].fa].ch[1];}
    
    
        inline void Rotate(int x) {
            int y = s[x].fa, z = s[y].fa, chk = get(x);
    
            //y与x的子节点相连
            s[y].ch[chk] = s[x].ch[chk ^ 1];
            s[s[x].ch[chk ^ 1]].fa = y;
    
            //x与y父子相连
            s[x].ch[chk ^ 1] = y;
            s[y].fa = x;
    
            // x与y的原来的父亲z相连
            s[x].fa = z;
            if(z) s[z].ch[y == s[z].ch[1]] = x;
    
            //只有x和y的sz变化了
            maintain(y);
            maintain(x);
        }
        //将当前节点转移到相应节点
        inline void splay(int x,int y) {
            for(int f = s[x].fa; f != y; Rotate(x),f = s[x].fa){
                if(s[f].fa != y) Rotate(get(x) == get(f) ? f : x);
            }
            if(y==0) rt = x;
        }
    
        //查询第k个数
        inline int getKth(int k) {
            int now = rt;
            while(true){
                pushDown(now);
                if(s[now].ch[0] && k <= s[s[now].ch[0]].sz){
                    now = s[now].ch[0];
                }else{
                    k -= s[s[now].ch[0]].sz + 1;
                    if(k <= 0){
                        return now;
                    }
                    now=s[now].ch[1];
                }
            }
        }
        //初始化
        int build(int f,int l,int r){
            if(l>r) return 0;
            int x = ++tot;
            s[x].fa = f;
            s[x].val = arr[Mid];
            s[x].tag = 0;
            s[x].ch[0] = build(x,l,Mid - 1);//与线段树不同,该节点包括了mid这个值
            s[x].ch[1] = build(x,Mid + 1,r);
            maintain(x);
            return x;
        }
        //下传标记
        void pushDown(int x) {
            if(x&&s[x].tag) {
                s[s[x].ch[0]].tag ^= 1;
                s[s[x].ch[1]].tag ^= 1;
                swap(s[x].ch[0],s[x].ch[1]);
                s[x].tag = 0;
            }
        }
        //翻转(l,r),先锁定区间
        void Reverse(int l,int r) {
            int x = getKth(l),y = getKth(r+2);
            splay(x,0);
            splay(y,x);
            s[s[y].ch[0]].tag ^= 1;
        }
        //中序遍历输出
        void solve(int now){
            pushDown(now);
            if(s[now].ch[0]) solve(s[now].ch[0]);
            if(s[now].val != -inf && s[now].val != inf) printf("%d ",s[now].val);
            if(s[now].ch[1]) solve(s[now].ch[1]);
        }
    
        void debug(int n){
            int x = getKth(1),y=getKth(5);
            splay(x,0);
            splay(y,x);
        }
    }st;
    int main(){
        int n,k;
        scanf("%d %d",&n,&k);
        for(int i=1;i<=n;++i) arr[i+1] = i;
        arr[1] = -inf;
        arr[n+2] = inf;
        rt = st.build(0,1,n+2);
        int l=0,r=0;
        for(int i=1;i<=k;++i){
            scanf("%d %d",&l,&r);
            st.Reverse(l,r);
        }
        st.solve(rt);
        return 0;
    }
    
  • 相关阅读:
    JavaScript 闭包
    JavaScript for循环
    JavaScript switch语句
    JavaScript if...else 语句
    JavaScript流程控制语句脑图
    JavaScript比较和逻辑运算符
    JavaScript运算符
    记录一下获取浏览器可视区域的大小的js
    20181016记录一次前端布局
    20181015记录一个简单的TXT日志类
  • 原文地址:https://www.cnblogs.com/smallocean/p/12417066.html
Copyright © 2011-2022 走看看