这道题没有注明出处,因为各大OJ都有,就随意找地儿测试了(e.g 洛谷,BZOJ……)
文艺平衡树
描述
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
翻转一个区间,例如原有序序列是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次变换后的结果
样例输入
5 3
1 3
1 3
1 4
样例输出
4 3 2 1 5
提示
n,m<=100000
分析
面对这样一道题,其题目便给了我们提示:需要用到平衡树的知识
又因为这是一个对区间进行操作的活,我们就需要在平衡树中按区间下标进行构造
这样一来,当操作到区间【l,r】的时候我们就把 l-1 旋到根的位置上去,再把 r + 1旋到根的右儿子位置处
如此,我们需要的区间【l,r】便都处在r+1的左儿子中了,然后有什么操作的话,我们就对区间进行标记
而这道题就是需要一个类似lazy_tag的懒标记,来记录这个区间是否需要被翻转
建图的时候呢就按照中序遍历的思想建,反正最后呈现出来的图应该是中序遍历一遍是原序列
存疑:为什么一开始建图的时候必须要多一些虚点呢???
代码
感觉又长又繁琐,但其实只要分块,一部分一部分的,一个函数一个函数的,慢慢码,就好了
#include<bits/stdc++.h>
#define in read()
#define N 500009
using namespace std;
int n,m,tot=0,rt=1;
int sze[N],num[N],ch[N][2],f[N],a[N],lzy[N];
inline int read(){读优就省略了吧}
int newnode(int x){//新建一个节点
++tot;lzy[tot]=0;
sze[tot]=num[tot]=1;ch[tot][0]=ch[tot][1]=f[tot]=0;
a[tot]=x;return tot;
}
void pushup(int x){ sze[x]=sze[ch[x][0]]+sze[ch[x][1]]+num[x];}//更新
int build(int l,int r,int fa){//按照类似中序遍历的思路建图,这样最后输出的时候就会比较方便
if(l>r) return 0;
int mid=l+r>>1;
int x=newnode(mid);
f[x]=fa;
ch[x][0]=build(l,mid-1,x);
ch[x][1]=build(mid+1,r,x);
pushup(x);//其实学过线段树的同学就会很容易明白这个代码了
return x;
}
void pushdown(int k){
if(lzy[k]){
swap(ch[k][0],ch[k][1]);//因为这个lazy是指交不交换
lzy[ch[k][0]]^=1;//异或的话,如果上次是0这次就是1,上次是1这次就是0
lzy[ch[k][1]]^=1;
lzy[k]=0;//清空父亲的标记
}
}
int find(int x,int o){//the xth
pushdown(o);//下放标记,只要会用到儿子的信息就都应该下放
if(!x) return 0;
if(x>sze[o]) return find(sze[o],o);
int lsiz=sze[ch[o][0]];
if(x==lsiz+1) return o;
if(x<=lsiz) return find(x,ch[o][0]);
else return find(x-lsiz-num[x],ch[o][1]);//num[i]表示i出现的次数
}
int which(int x){ return x==ch[f[x]][1];}//判断x相对于它父亲的位置关系,是左儿子还是右儿子
void rotate(int x,int &rt){//因为有了上面的判断,zigzag就可以合在一起写了
int y=f[x],z=f[y],d=which(x),d1=which(y);
pushdown(y);pushdown(x);//下放
if(y==rt) rt=x;else ch[z][d1]=x;//1
ch[y][d]=ch[x][d^1];f[ch[x][d^1]]=y;//2
ch[x][d^1]=y;f[y]=x;f[x]=z;//3
//这三行代码就是旋转的关键了,画个图,每次选的时候不仅要换父亲还要更新儿子
pushup(y);pushup(x);
}
void splay(int x,int &rt){
int y=f[x],z=f[y];
while(x!=rt){
if(y!=rt){
if((x==ch[y][1])==(y==ch[z][1])) rotate(y,rt);
else rotate(x,rt);
}
rotate(x,rt);
y=f[x];z=f[y];
}
}
void flip(int l,int r){
int x=find(l,rt);//本来应该找l-1和r+1的,
//但由于不知道出什么玄学的问题,必须要在建图的时候建两个虚点,所以就变成这样咯
int y=find(r+2,rt);
splay(x,rt);splay(y,ch[x][1]);
lzy[ch[y][0]]^=1;
}
void dfs(int x){//输出的时候按照中序遍历(左儿子,根,右儿子)的顺序输出
if(!x) return ;
pushdown(x);
dfs(ch[x][0]);
if(a[x]>=1&&a[x]<=n) printf("%d ",a[x]);
dfs(ch[x][1]);
}
int main(){
n=in;m=in;
int l,r;
build(0,n+1,0);
while(m--){
l=in;r=in;
flip(l,r);
}
dfs(rt);
return 0;
}
牢骚
天呐,数据结构的题怎么那么难调啊……本来代码就长,而且还看不出来错误???
好吧,主要还是思想没有完全彻悟
其次,不要将希望寄托于调试,要用自己清晰的思路去写,就不怕了
这道题应该也算一道板子题了,用类似于线段树区间加的思路,记录一个lazy_tag,标记这个区间这一段是否被翻转过