【学校OJ】 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] 数据保证 1<=l<=r<=n 
数据规模:n, m <= 100000

输出

输出一行n个数字,表示原始序列经过m次变换后的结果

样例输入

 (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

5 3
1 3
1 3
1 4

样例输出

4 3 2 1 5

    把普通青年解决后,就开始着手于文艺青年噜~

    文艺青年很单(e)纯(xin),他只让我们交换位置。那么就用splay经典操作——反转区间来解决吧!

    都说splay是玄学,所以查询速度完全不可能比科学的avl快,那它就只能从代码长度和一些刁难性操作取胜了。最为出名的splay操作怎么可能只是拿来调整高度的?其实,它还能用一个数将树切成两份!这很好解释,因为二叉排序树的基本性质,左子树全部比根小,右子树全部比根大,那么如果要把一个区间[a,b]放到一起,就可以这样操作:

    1)因为[a,b]中的每一个都比a-1大,那么我们可以先把(a-1)splay到根,这样,[a,b]中的每一个元素都在右子树中了!

    2)再看,[a,b]中的每一个又都比b+1小,那么就可以把(b+1)splay到根的右儿子,就这样,[a,b]的所有元素都在(b+1)的左子树,而这棵左子树里又没有其他节点,这时就可以进行翻转了(把这棵树里的所有节点进行左右子树交换)!

    我们可以用一个find函数,通过新增一个splay树成员size来计算在树中的排名(注意随时维护),每次翻转[a,b],按如上操作即可。

    但是,如果每次都翻转到底,肯定会牺牲大量时间,我们基于类似线段树的lazy操作,可以将一棵树的根进行标记。每次就将标记下传即可(splay时、rotate时、find时和输出树时)。

    当然,题目中会有一些细节(比如需要多增加两个节点0和n+1,并为了保险集体加1)。但就不多说了,自己体会吧!

#include<cstdio>
#include<algorithm>
using namespace std;
int root,cnt,n,m,sum,c;
int getint()//读入优化,不解释
{
	int p=0;
	bool f=0;
	char c=getchar();
	while((c<'0'||c>'9')&&c!='-')
		c=getchar();
	if(c=='-')
	{
		f=1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		p=p*10+(c-'0');
		c=getchar();
	}
	if(f)p=-p;
	return p;
}
struct Splay
{
	int ch[2],f,p,lazy,size;
}s[100005];
void write(int q)//用来维护lazy,每次将标记下传
{
	if(!q)return;//边界
	if(s[q].lazy)
	{
		swap(s[q].ch[0],s[q].ch[1]);//交换儿子
		s[s[q].ch[0]].lazy^=1;//标记下传
		s[s[q].ch[1]].lazy^=1;
		s[q].lazy=0;
	}
}
void again(int q)//用来维护size
{
	if(q)s[q].size=s[s[q].ch[0]].size+s[s[q].ch[1]].size+1;
}
void rotate(int x)//旋转(左旋右旋合体)
{
	int y=s[x].f;
	int z=s[y].f;
	if(x==0||y==0)return;
	int wh=(x==s[y].ch[1]);//判断该左旋还是右旋
	s[y].ch[wh]=s[x].ch[!wh];
	if(s[y].ch[wh])s[s[y].ch[wh]].f=y;
	s[x].ch[!wh]=y;
	s[x].f=z;
	s[y].f=x;
	if(z)s[z].ch[s[z].ch[1]==y]=x;
	again(y);//y的儿子数量发生了变化,所以要更新size
}
void splay(int x,int goal)//不解释
{
	write(x);//先下传标记,不然顶上去就悲哀了
	for(int y;(y=s[x].f)!=goal;rotate(x))
	{
		int z;
		if((z=s[y].f)!=goal)
		{
			if((x==s[y].ch[1])==(y==s[z].ch[1]))
				rotate(x);
			else
				rotate(y);
		}
	}
	if(goal==0)root=x;
	again(x);//x的儿子数量发生了变化,需要重新维护
}
void insert(int &q,int a,int last)//插入不解释
{
	if(q==0)
	{
		s[(q=++cnt)].p=a;
		s[cnt].f=last;
		splay(cnt,0);
		return;
	}
	if(s[q].p==a)
	{
		splay(q,0);
		return;
	}
	if(s[q].p>a)
		insert(s[q].ch[0],a,q);
	else
		insert(s[q].ch[1],a,q);
	again(q);
}
int find(int k,int q)//寻找数组中排名第k的
{
	write(q);//每时每刻都要下传
	if(s[s[q].ch[0]].size+1==k)return q;
	if(s[s[q].ch[0]].size+1<k)return find(k-s[s[q].ch[0]].size-1,s[q].ch[1]);
	else return find(k,s[q].ch[0]);
}
void play(int l,int r)//反转区间[l,r],请参考题解
{
	splay(find(l-1,root),0);
	splay(find(r+1,root),root);
	s[s[s[root].ch[1]].ch[0]].lazy^=1;
}
void print(int q)//打印不解释
{
	write(q);
	if(q==0)return;
	print(s[q].ch[0]);
	if(s[q].p>1&&s[q].p<=n+1)
	{
		if(c)printf(" ");
		printf("%d",s[q].p-1);//输出时减个1
		c=1;
	}
	print(s[q].ch[1]);
}
int main()
{
	n=getint();
	m=getint();
	for(int i=1;i<=n+2;i++)
		insert(root,i,0);
	for(int i=1;i<=m;i++)
	{
		int a=getint()+1,b=getint()+1;//集体加个1
		play(a,b);
		/*print(root);
		printf(" end\n");
		c=0;*/
	}
	print(root);
}


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值