[平衡树-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;
}

原文地址:https://www.cnblogs.com/smallocean/p/12417066.html

时间: 2024-09-30 08:18:22

[平衡树-Splay]文艺平衡树_区间翻转的相关文章

splay 文艺平衡树 (数据结构)

题目大意:略 splay维护区间翻转裸题,为了减少不必要的麻烦,多插入两个点,分别是0和n+1 每次找区间的第K个值,就在splay上二分即可 顺便学了一下splay的完美建树,而且splay有一些小函数可以宏定义或者用inline,跑得飞快 最后跑一遍中序遍历即可 1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #define N 100100 5 #define il inline

BZOJ3223: Tyvj 1729 文艺平衡树 [splay]

3223: Tyvj 1729 文艺平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3595  Solved: 2029[Submit][Status][Discuss] Description 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 Input 第一行为n,m n表示初始序列有n个数,这个序列依次

Tyvj P1729 文艺平衡树 Splay

题目: http://tyvj.cn/p/1729 P1729 文艺平衡树 时间: 1000ms / 空间: 131072KiB / Java类名: Main 背景 此为平衡树系列第二道:文艺平衡树 描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入格式 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n)  m表示翻转操作次数

P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n?1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

洛谷P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

P3391 【模板】文艺平衡树(Splay)新板子

P3391 [模板]文艺平衡树(Splay) 题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,

洛谷P3391 【模板】文艺平衡树(Splay)(FHQ Treap)

题目背景 这是一道经典的Splay模板题——文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n−1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

【模板】文艺平衡树(Splay)

题目背景 这是一道经典的Splay模板题--文艺平衡树. 题目描述 您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1 输入输出格式 输入格式: 第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2, \cdots n-1,n)(1,2,?n?1,n) m表示翻转操作次数 接下来m行每行两个数 [l,r][l,r] 数据保证 1 \leq l \leq r

洛谷 P3391 【模板】文艺平衡树(Splay)

题目背景 这是一道经典的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][l,r] 数据保证 1≤l≤r≤n 输出格式: 输出一行n个数字,表示原始序列经过m次变换后的结果