题目描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
翻转一个区间,例如原有序序列是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] 数据保证 1 ≤ l ≤ r ≤ n
输出格式:
输出一行n个数字,表示原始序列经过m次变换后的结果
解题思路
我们现在需要这样一种数据结构,它可以快速反转指定区间的元素,并能够快速找到第k个元素的数值。考虑到序列中的元素是1~n,所以我们用常量SZ = 1e5+10来规定范围。
我们可以在序列1~n建立一棵树,这颗树上每个节点有一个变量size表示以该节点为根的树有多少个元素。如果我们想找排名第k的元素,那么每次只需和当前节点的size比较一下,就可以决定是走左子树还是右子树了。所以我们需要维护这棵树的size,并设法解决当反转一个区间[l,r]时,如何维护size。
由于旋转操作(rotate)并不会改变每个节点左边或右边的size总数,所以并不会影响每个元素的排名,因此旋转操作是被允许的。既然旋转操作被允许,那么splaying操作也可以被允许。
综上所述,我们需要实现如下2个主函数:
getValByRank(k): 获得排名第k的元素的值
reverse(a,b): 反转区间[a,b]内的元素
为了实现上述2个函数,我们还需要一些辅助函数:
buildTree(): 建树操作
splay(x): 将节点x旋转至根
init(): 负责初始化
rotate(x): 旋转节点x和其父节点连接的边,用于辅助splay
update(x): 更新节点x的size,用于辅助rotate
spread(x): 延迟标记的更新(类似于线段树),主要用于向下更新反转操作
join(x,y) 合并两棵树x、y,并返回新树的根
split(x,y,a) 将树x按照a分为两棵子树x和y
split3(x,y,z,a,b): 将树按照[1,a),[a,b],(b,n]分为x、y、z三棵子树
join3(x,y,z): 将x、y、z三棵子树合并
如何实现反转
如果想要反转区间[a,b],那么很显然原区间被分为了三份:[1,a) , [a,b] , (b,n],我们可以通过给区间[a,b]打上反转记号rev = 1,来表示该区间需要被反转。当我们访问一个节点的size时,我们就从上而下的传递rev记号,并决定是否交换每个节点的左右子树。这样做的好处是可以避免重复反转,只需在查询时对查询路径上每个点判断一次rev即可。(类似延迟标记)
所以我们需要首先将原树[1,n]分成代表[1,a),[a,b],(b,n]的三棵子树x、y、z,并对代表区间[a,b]的树根打上延迟标记rev = 1。当我们每次需要访问节点x的size时,我们spread(x)。
代码实现
#include<cstdio>
const int SZ = 1e5+10;
struct SplayTree{
int rt,cnt,off; //根、节点个数、偏移量
/*
type: 0左儿子,1右儿子,ch[x][0]为左儿子编号,ch[x][1]为x右儿子编号
size[x]代表以x为根的子树大小,fa[x]为节点x父亲编号
tmp为临时数组,rev为反转标记数组
*/
int type[SZ],ch[SZ][2],size[SZ],fa[SZ];
int tmp[SZ],rev[SZ];
//获得排名为k的元素
int getValByRank(int x,int k){
while(true){
spread(x);
if(size[ch[x][0]]+1 == k) break;
else if(k <= size[ch[x][0]]) x = ch[x][0];
else{
k -= size[ch[x][0]] + 1;
x = ch[x][1];
}
}
return x;
}
//反转区间[a,b]
void reverse(int a,int b){
int x,y;
split3(rt,x,y,a+off,b+off);
rev[x] ^= 1;
join3(rt,x,y);
}
void buildTree(int l,int r,int pa){
if(l > r) return;
int mid = (l+r)/2; //取当前中点作为该区间的根
if(mid < pa) ch[pa][0] = mid,type[mid] = 0;
else ch[pa][1] = mid,type[mid] = 1;
fa[mid] = pa,size[mid] = 1;
if(l == r) return;
buildTree(l,mid-1,mid);
buildTree(mid+1,r,mid);
update(mid);
}
void init(int n,int offset){
rt = (n+3)/2; //树根,取区间中点
off = offset; //偏移量,设起点无穷小
type[rt] = 2; //0表示为左子树,1为右子树,2为树根
size[rt] = n+2; //根节点的大小是n+2
buildTree(1,rt-1,rt);
buildTree(rt+1,n+2,rt);
}
//将节点x移到树根
void splay(int x){
int scnt = 0;
tmp[scnt++] = x;
for(int i = x;type[i] != 2;i = fa[i])
tmp[scnt++] = fa[i];//这样就能保存x到根路径上所有节点
for(int i = scnt -1;i >= 0;i--) spread(tmp[i]);
//分别为zig zig-zig zig-zag旋转
while (type[x] != 2) {
int y = fa[x];
if (type[x] == type[y]) rotate(y);//此处不需要update(y),下一次rotate(x)中会update(y)
else rotate(x);
if (type[x] == 2) break;
rotate(x);
}
update(x);
}
//对x和fa[x]间的边做反转
void rotate(int x){
int t = type[x],y = fa[x],z = ch[x][1-t];
type[x] = type[y]; //x顶替y的位置,类型自然相同
fa[x] = fa[y]; //把y的父节点改成x的父节点
if(type[x] != 2) ch[fa[x]][type[x]] = x;//将x与其父节点连接
ch[x][1-t] = y; fa[y] = x; type[y] = 1-t;//将y改成x的子树
ch[y][t] = z; //将x原来的一棵子树交给y
if(z) fa[z] = y, type[z] = t;//如果子树z不为空,要更新信息
update(y); //y的size有所改变,需要更新
//虽然x的size也改变了,但是不用急着更新,因为此时的x可能是下一轮的y
}
//更新节点x的size值
inline void update(int x){
size[x] = size[ch[x][0]] + 1 + size[ch[x][1]];
}
//向下传递延迟标记
inline void swap(int &a,int &b){
int t = a;a = b;b = t;
}
inline void spread(int x){
if(rev[x] == 0) return;
rev[x] = 0; //清楚交换标记
rev[ch[x][0]] ^= 1;
rev[ch[x][1]] ^= 1; //向下传递交换标记
swap(ch[x][0],ch[x][1]);//交换左右儿子
type[ch[x][0]] = 0; //别忘了更新儿子的类型
type[ch[x][1]] = 1;
}
//将树x和y合并成新树x
inline void join(int &x,int y){
x = getValByRank(x,size[x]);
splay(x); //将节点x移至根
ch[x][1] = y,type[y] = 1,fa[y] = x;//将y变为x的右儿子
update(x);//节点x的size改变了
}
//将以x为根的树按照a分成两棵树x和y
void split(int &x,int &y,int a){
y = getValByRank(x,a+1);
splay(y);
x = ch[y][0];
type[x] = 2;
ch[y][0] = 0; //左子树分离出去
update(y);
}
void split3(int &x,int &y,int &z,int a,int b){
split(x,z,b);
split(x,y,a-1); //这里要是a-1,
}
void join3(int &x,int y,int z){
join(y,z);
join(x,y);
}
int getAns(int rank){ //求本题答案
return getValByRank(rt,rank+off)-off;
}
}sp;//树要建立在外面,占全局变量的空间
int main(){
int n,m,a,b;
scanf("%d%d",&n,&m);
sp.init(n,1);
while(m--){
scanf("%d%d",&a,&b);
sp.reverse(a,b);
}
for(int i = 1;i <= n;i++) printf("%d ",sp.getAns(i));
return 0;
}