题目链接 https://www.luogu.org/problemnew/show/P3391
Splay 基础操作 https://www.cnblogs.com/yjkhhh/p/11012895.html
\(Splay\)上的区间翻转
首先, 这里的 \(Splay\)维护的是一个序列的顺序, 每个结点即为序列中的一个数, 序列的顺序即为 \(Splay\)的中序遍历
那么如何实现区间翻转呢?
对于一次区间翻转操作 \(rev(l,r)\), 显然先要找到 \(l\)和 \(r\)在 \(Splay\)中的位置
然后把 \(l-1\) \(splay\)到根结点, 再把 \(r+1\) \(splay\)到 \(l\)的右儿子的位置
那么区间 \([l,r]\)就到了一个子树上, 即 \(ch[r+1][0]\)
这样就可以翻转了
把序列上的 \([l,r]\)翻转即为把 \(Splay\)上 \([l,r]\)的子树给翻过来
当然不是这么翻..
没错! 是这么翻.. 好诡异
我们只需要把整棵子树的所有节点的左右儿子脚滑交换一下就可以了, 可以维护一个类似于线段树区间操作的 \(tag\)标记
只需要在查找和最后中序遍历时 \(push\_down\)就可以了
完整代码:
- #include<iostream>
- #include<cstring>
- #include<cstdio>
- using namespace std;
- const int MAXN=100010;
- const int INF=0x3f3f3f3f;
- inline int read(){
- int x=0;char c=getchar();
- while(c<'0')c=getchar();
- while(c>='0')x=(x<<1)+(x<<3)+c-'0',c=getchar();
- return x;
- }
- int n,m,root,Size;
- int ch[MAXN][2],f[MAXN],size[MAXN],val[MAXN],tag[MAXN];
- inline int get_w(int x){
- return ch[f[x]][1]==x;
- }
- inline void push_up(int x){
- if(x)size[x]=size[ch[x][0]]+size[ch[x][1]]+1;
- }
- inline void rotate(int x){
- int fa=f[x],gfa=f[f[x]],ws=get_w(x);
- ch[fa][ws]=ch[x][ws^1];f[ch[fa][ws]]=fa;
- ch[x][ws^1]=fa;f[fa]=x;f[x]=gfa;
- if(gfa)ch[gfa][ch[gfa][1]==fa]=x;
- push_up(fa);push_up(x);
- }
- inline void Splay(int x,int goal){
- for(int fa;fa=f[x];rotate(x))
- if(fa==goal)break;
- else if(f[fa]!=goal) rotate(get_w(x)==get_w(fa)?fa:x);
- if(goal==0)root=x;
- }
- inline void insert(int x){
- if(root==0){
- root=++Size;val[Size]=x;
- size[Size]=0;return;
- }
- int now=root,fa=0;
- while(now)fa=now,now=ch[now][x>val[now]];
- val[++Size]=x;
- f[Size]=fa;ch[fa][x>val[fa]]=Size;
- size[Size]=1;Splay(Size,0);
- }
- inline void push_down(int p){ // 标记下放
- if(tag[p]){
- swap(ch[p][0],ch[p][1]);
- tag[ch[p][0]]^=1;
- tag[ch[p][1]]^=1;
- tag[p]=0;
- }
- }
- inline int find_num(int x){ // 找到序列的第 x 个数的位置
- if(!root)return 0;
- int now=root;
- while(1){
- push_down(now);
- if(x<=size[ch[now][0]])now=ch[now][0];
- else{
- int temp=size[ch[now][0]]+1;
- if(x<=temp)return now;
- else x-=temp,now=ch[now][1];
- }
- }
- }
- void print(int x){ // 输出答案(中序遍历一遍)
- if(!x)return;
- push_down(x);
- print(ch[x][0]);
- if(val[x]!=INF&&val[x]!=-INF)
- printf("%d",val[x]);
- print(ch[x][1]);
- }
- void reserve(int l,int r){ // 翻转区间[l,r]
- int x=find_num(l);
- int y=find_num(r+2);
- Splay(x,0);Splay(y,x);
- tag[ch[ch[root][1]][0]]^=1;
- }
- int main(){
- n=read();m=read();
- insert(-INF);insert(INF); // 翻转 [1,x] 或[x,n]时需要用到 0 或 n
- for(int i=1;i<=n;++i)insert(i);
- int l,r;
- while(m--){
- l=read();r=read();
- reserve(l,r);
- }
- print(root);
- puts("");
- return 0;
- }
[洛谷 P3391] 文艺平衡树 --Splay 学习笔记(二)
来源: http://www.bubuko.com/infodetail-3093435.html