[平衡树-Splay]文艺平衡树_区间翻转

Posted smallocean

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[平衡树-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;
}

以上是关于[平衡树-Splay]文艺平衡树_区间翻转的主要内容,如果未能解决你的问题,请参考以下文章

模板文艺平衡树(Splay)

文艺平衡树(splay)

bzoj3223: Tyvj 1729 文艺平衡树(splay翻转操作)

3223. 文艺平衡树平衡树-splay

P3391 模板文艺平衡树(Splay)

蒟蒻的splay2之文艺平衡树