AcWing 2437:Splay树

【题目来源】
https://www.acwing.com/problem/content/description/2439/

【题目描述】
给定一个长度为 n 的整数序列,初始时序列为 {1,2,…,n−1,n}。
序列中的位置从左到右依次标号为 1∼n。
我们用 [l,r] 来表示从位置 l 到位置 r 之间(包括两端点)的所有数字构成的子序列。
现在要对该序列进行 m 次操作,每次操作选定一个子序列 [l,r],并将该子序列中的所有数字进行翻转。
例如,对于现有序列 1 3 2 4 6 5 7,如果某次操作选定翻转子序列为 [3,6],那么经过这次操作后序列变为 1 3 5 6 4 2 7。
请你求出经过 m 次操作后的序列。

【输入格式】
第一行包含两个整数 n,m。
接下来 m 行,每行包含两个整数 l,r,用来描述一次操作。

【输出格式】
共一行,输出经过 m 次操作后的序列。

【数据范围】
1≤n,m≤10^5,1≤l≤r≤n

【输入样例】
6 3
2 4
1 5
3 5

【输出样例】
5 2 1 4 3 6

【算法分析】
● 目前,在信息学竞赛里,最常用的平衡树是 Splay Tree(伸展树)。
Splay Tree 的最大特点是可以把某个结点旋转到指定位置,特别是可以旋转到树根的位置称为新的树根。因此,如果需要经常查询和使用某个数,可以把它旋转到根结点,这样下次访问它时,只需查一次就OK了。
Treap虽然代码简单,但功能有限,很多操作实现不了。
● Splay Tree 是
动态树树链剖分的基础。
左旋右旋是 Splay Tree 的两个基本操作。它在保证 Splay Tree 的中序遍历序列不变的前提下,调整 Splay Tree 的高度,从而维护 Splay Tree 的平衡。
● 对 Splay Tree 的结点进行操作(如插入结点、查找结点)后,需将此结点旋转到 Splay Tree 的树根,并维护 Splay Tree 的中序遍历序列不变。
● Splay Tree 的核心操作是 rotate(x,k),即将结点 x 旋转到结点 k 的下方。其他所有操作,都是围绕着 rotate(x,k) 进行的。

【算法代码】

#include <iostream>
using namespace std;

const int maxn=1e5+5;
int n,m;
int root,idx;

struct splay_node {
    int ch[2];
    bool flag;
    int size;
    int val;
    int fa; //father node
} tr[maxn];

void push_up(int x) {
    tr[x].size=tr[tr[x].ch[0]].size+tr[tr[x].ch[1]].size+1;
}

void push_down(int x) {
    if(tr[x].flag) {
        swap(tr[x].ch[0],tr[x].ch[1]);
        tr[tr[x].ch[0]].flag^=1,tr[tr[x].ch[1]].flag^=1;
        tr[x].flag=0;
    }
}

int get(int x) {
    return tr[tr[x].fa].ch[1]==x;
}

void rotate(int x) {
    int y=tr[x].fa,z=tr[y].fa;
    int tx=get(x),ty=get(y);
    tr[z].ch[ty]=x,tr[x].fa=z;
    tr[y].ch[tx]=tr[x].ch[tx^1],tr[tr[x].ch[tx^1]].fa=y;
    tr[x].ch[tx^1]=y,tr[y].fa=x;
    push_up(y),push_up(x);
}

void splay(int x,int k) {
    while(tr[x].fa!=k) {
        int y=tr[x].fa,z=tr[y].fa;
        if(z!=k) {
            if(get(x)^get(y)) rotate (x);
            else rotate(y);
        }
        rotate(x);
    }
    if(!k) root=x;
}

void insert(int val) {
    int u=root,fa=0;
    while(u) {
        fa=u;
        u=tr[u].ch[val>tr[u].val];
    }
    u=++idx;
    if(fa) tr[fa].ch[val>tr[fa].val]=u;
    tr[u].val=val,tr[u].fa=fa;
    splay(u,0);
}

int get_key(int k) {
    int u=root;
    while(true) {
        push_down(u);
        if(tr[tr[u].ch[0]].size>=k) u=tr[u].ch[0];
        else if(tr[tr[u].ch[0]].size+1==k) return u;
        else {
            k-=tr[tr[u].ch[0]].size+1;
            u=tr[u].ch[1];
        }
    }
    return -1;
}

void output(int x) {
    push_down(x);
    if(tr[x].ch[0]) output(tr[x].ch[0]);
    if(tr[x].val>=1 && tr[x].val<=n) printf("%d ",tr[x].val);
    if(tr[x].ch[1]) output(tr[x].ch[1]);
}

int main() {
    scanf("%d %d",&n,&m);
    for(int i=0; i<=n+1; i++) insert(i); //i<=n+1. if i<=n, TLE

    while(m--) {
        int le,ri;
        cin>>le>>ri;
        le=get_key(le),ri=get_key(ri+2);
        splay(le,0), splay(ri,le);
        tr[tr[ri].ch[0]].flag^=1;
    }

    output(root);
    return 0;
}

/*
in:
6 3
2 4
1 5
3 5

out:
5 2 1 4 3 6
*/



【参考文献】
https://www.cnblogs.com/gzh-red/p/11011557.html
https://www.acwing.com/solution/content/148899/
https://www.acwing.com/blog/content/26008/
https://www.cnblogs.com/quanjun/p/16966987.html
https://www.acwing.com/solution/content/50494/
https://blog.csdn.net/qq_25426559/article/details/122710135




 

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值