1003-Splay-文艺平衡树

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/82932607

这道题没有注明出处,因为各大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,标记这个区间这一段是否被翻转过

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/82932607
今日推荐