题目背景
这是一道经典的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次变换后的结果
**************************
题目分析:
传送门
splay经典区间操作
基础splay伸展,建树,操作等操作看这里
Splay—平衡树学习笔记
毕竟splay是个挺暴力的玩意儿
所以肯定不能直接暴力反转区间
所以我们借用线段树思想
用lzy[u]=1表示以u为根的子树需要反转
然而问题是怎么找到需要反转的区间呢
假设我们要得到 ll-rr 区间
我们先找到当前序列第ll-1个元素(设为x)和第rr+1个元素(设为y)
将x旋转到rt,再将y旋转到ch[rt][1]
这时ch[y][0],也就是根的右儿子的左子树
就是我们要的区间
仔细思考,是不是很神奇
注意因为要旋转ll-1与rr+1
所以序列需要加两个哨兵结点1与n+2
而原序列就变成了2-n+1
记得每次输入的操作区间要先加1
到这里我们只要给ch[y][0]打上标记就好了
然后在需要的时候下放标记
void push(int p)
{
if(!lzy[p]) return;
swap(ch[p][0],ch[p][1]);//交换左右子树
lzy[ch[p][1]]^=1; lzy[ch[p][0]]^=1;//下放标记
lzy[p]=0;
}
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int read()
{
int x=0,f=1;
char ss=getchar();
while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
return f*x;
}
int n,m;
int rt;
int size[100010],fa[100010],ch[100010][2];
int lzy[100010];
void update(int p)
{
size[p]=size[ch[p][0]]+size[ch[p][1]]+1;
}
void push(int p)
{
if(!lzy[p]) return;
swap(ch[p][0],ch[p][1]);//交换左右子树
lzy[ch[p][1]]^=1; lzy[ch[p][0]]^=1;//下放标记
lzy[p]=0;
}
void build(int p,int ll,int rr)
{
if(ll>rr) return;
int mid=ll+rr>>1;
fa[mid]=p; size[mid]=1;
ch[p][mid>p]=mid;
if(ll==rr) return;
build(mid,ll,mid-1);build(mid,mid+1,rr);
update(mid);
}
int find(int p,int k)
{
push(p);
int ss=size[ch[p][0]];
if(k==ss+1) return p;
if(k<=ss) return find(ch[p][0],k);
else return find(ch[p][1],k-ss-1);
}
void rotate(int& p,int x) {
int y=fa[x],z=fa[y];
int t=(ch[y][0]==x);
if(y==p) p=x;
else if(ch[z][0]==y) ch[z][0]=x;
else ch[z][1]=x;
fa[y]=x; fa[ch[x][t]]=y; fa[x]=z;
ch[y][t^1]=ch[x][t]; ch[x][t]=y;
update(y);update(x);
}
void splay(int& p,int x)
{
while(x!=p)
{
int y=fa[x],z=fa[y];
if(y!=p)
{
if((ch[y][0]==x)^(ch[z][0]==y)) rotate(p,x);
else rotate(p,y);
}
rotate(p,x);
}
}
void rev(int ll,int rr)
{
int x=find(rt,ll-1),y=find(rt,rr+1);//找到第ll-1和rr+1个元素编号
splay(rt,x);
splay(ch[x][1],y); //伸展
lzy[ch[y][0]]^=1;//标记
}
int main() {
n=read();m=read();
rt=n+3>>1;//因为加了两个哨兵结点,所以rt是(n+3)/2
build(rt,1,n+2);
while(m--)
{
int ll=read(),rr=read();
rev(ll+1,rr+1);//操作区间后移
}
for(int i=2;i<=n+1;++i)
printf("%d ",find(rt,i)-1);//输出相当于从1开始每次找到第k个元素
return 0;//当然也可以按照中序遍历输出
}