bzoj3223: Tyvj 1729 文艺平衡树 (非旋Treap)

题目描述

bzoj3223

Description
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:翻转一个区间,例如原有序序列是5 4 3 2 1,翻转区间是[2,4]的话,结果是5 2 3 4 1

Input
第一行为n,m n表示初始序列有n个数,这个序列依次是(1,2……n-1,n) m表示翻转操作次数
接下来m行每行两个数[l,r] 数据保证 1<=l<=r<=n

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

Sample Input
5 3
1 3
1 3
1 4

Sample Output
4 3 2 1 5

HINT
N,M<=100000

前言

这道题用splay来写其实非常好写,但为了装逼所以去学了非旋Treap
相比splay来说,普通的Treap不能支持区间修改,还不见得好写多少
要你有何用
至于非旋Treap跟splay功能差不多,但支持可持久化
还算有点卵用

Treap

Treap=tree+heap,相比普通的二叉查找树多了一个属于堆的值
所以Treap要保证二叉查找树(左小右大)和堆(上小下大,这里指的是堆的权值)的性质

因为堆在随机情况下高度是 log ⁡ n \log n logn的,所以每次操作期望 log ⁡ n \log n logn
如果脸实在太黑也没办法

普通的Treap是用旋转来维护自身,这里不多讲
而非旋Treap则是用拆分合并来维护自身
所以两种Treap实际上差别很大

非旋Treap

就像splay的核心在于splay操作一样,非旋Treap的核心在于merge和split两个操作

merge(x,y)

就是把x和y所在的Treap合并成一颗Treap(保证x和y是所在Treap的根,且x中任意元素的关键字小于y中任意元素的关键字),新的Treap满足上面两条性质

若当前在merge(x,y)
这里写图片描述

如果heap[x]< heap[y],则x应该在y的上面,且x的关键字小于y,所以x要在y的左上
这里写图片描述
那么x的左子树就可以直接安置了,接着merge(x的右儿子,y),继续向下合并

如果heap[x]>heap[y],则x应该在y的下,且x的关键字小于y,所以x要在y的左下
这里写图片描述
同上,y的右子树直接放,然后merge(x,y的左儿子)

在实际操作中,为了方便起见可以记录当前位置的父亲和当前是父亲节点的哪个儿子
合并时注意下传标记,要判断不能往下走的情况(直接接上去就可以了)

void merge(int Fa,int son,int x,int y)//Fa和son表示当前位置的父亲和自己是哪个儿子
{
	down(x);//下传标记
	down(y);
	
	if (heap[x]<heap[y])//维护堆的性质
	{
		tr[Fa][son]=x;//接到父亲节点上去
		fa[x]=Fa;
		
		if (tr[x][1])
		merge(x,1,tr[x][1],y);
		else
		{//直接合并
			sum[x]+=sum[y];
			tr[x][1]=y;
			fa[y]=x;
		}
	}
	else
	{
		tr[Fa][son]=y;
		fa[y]=Fa;
		
		if (tr[y][0])
		merge(y,0,x,tr[y][0]);
		else
		{
			sum[y]+=sum[x];
			tr[y][0]=x;
			fa[x]=y;
		}
	}
	
	if (Fa) up(Fa);
}

split(t,k)

表示把t所在的Treap中前k个节点分离成一颗新的Treap,剩下的组成另一颗Treap
找到这些节点并不难,只需找到第k小的数,每次向右走则当前位置加上左子树都是分离的节点
这里写图片描述
然后可以这样连(每次要把跟原父亲的连边断开)
这里写图片描述
就变成了两棵树,之后从下往上维护子树大小就行了
在实际操作中,可以维护两棵树在路径上且深度最大的点,这样就可以直接接上去了

void split(int Fa1,int Fa2,int t,int k)//Fa1Fa2定义同上
{
	down(t);//下传标记
	
	if (sum[tr[t][0]]>=k)//向左走
	{
		if (fa[t])//断开和原父亲的边
		{
			tr[fa[t]][tr[fa[t]][1]==t]=0;
			up(fa[t]);
		}
		
		tr[Fa1][0]=t;
		fa[t]=Fa1;
		
		if (k && tr[t][0])//因为要连接剩余部分,所以要特判边界(即k=0)
		split(t,Fa2,tr[t][0],k);
		
		if (Fa1) up(Fa1);
	}
	else
	{//向右走,这里不考虑刚好找到第k大的情况,因为还要向下连接剩余部分
		if (fa[t])
		{
			tr[fa[t]][tr[fa[t]][1]==t]=0;
			up(fa[t]);
		}
		
		tr[Fa2][1]=t;
		fa[t]=Fa2;
		
		if (tr[t][1])
		split(Fa1,t,tr[t][1],k-sum[tr[t][0]]-1);
		
		if (Fa2) up(Fa2);
	}
}

修改

修改就很简单了,直接取出中间一段,之后在根上修改,最后依次合并三棵Treap
每次修改前要先查找出对应的数(因为会有删除插入翻转等操作)

code

/*
	Name: feixuan Treap
	Copyright: 
	Author: gmh77
	Date: 07/09/18 21:43
	Description: 
*/
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <ctime>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

int tr[100001][2];
int sum[100001];
bool rev[100001];
int fa[100001];
int heap[100001];
int n,Q,i,j,k,l,x,y,X,Fs,Ls;

void swap(int &x,int &y)
{
	int z=x;
	x=y;
	y=z;
}

void down(int t)
{
	if (rev[t])
	{
		swap(tr[t][0],tr[t][1]);
		rev[tr[t][0]]^=1;
		rev[tr[t][1]]^=1;
		
		rev[t]=0;
	}
}

void up(int t) {sum[t]=sum[tr[t][0]]+sum[tr[t][1]]+1;}

void merge(int Fa,int son,int x,int y)
{
	down(x);
	down(y);
	
	if (heap[x]<heap[y])
	{
		tr[Fa][son]=x;
		fa[x]=Fa;
		
		if (tr[x][1])
		merge(x,1,tr[x][1],y);
		else
		{
			sum[x]+=sum[y];
			tr[x][1]=y;
			fa[y]=x;
		}
	}
	else
	{
		tr[Fa][son]=y;
		fa[y]=Fa;
		
		if (tr[y][0])
		merge(y,0,x,tr[y][0]);
		else
		{
			sum[y]+=sum[x];
			tr[y][0]=x;
			fa[x]=y;
		}
	}
	
	if (Fa) up(Fa);
}

void split(int Fa1,int Fa2,int t,int k)
{
	down(t);
	
	if (sum[tr[t][0]]>=k)
	{
		if (fa[t])
		{
			tr[fa[t]][tr[fa[t]][1]==t]=0;
			up(fa[t]);
		}
		
		tr[Fa1][0]=t;
		fa[t]=Fa1;
		
		if (k && tr[t][0])
		split(t,Fa2,tr[t][0],k);
		
		if (Fa1) up(Fa1);
	}
	else
	{
		if (fa[t])
		{
			tr[fa[t]][tr[fa[t]][1]==t]=0;
			up(fa[t]);
		}
		
		tr[Fa2][1]=t;
		fa[t]=Fa2;
		
		if (tr[t][1])
		split(Fa1,t,tr[t][1],k-sum[tr[t][0]]-1);
		
		if (Fa2) up(Fa2);
	}
}

int find(int t,int k)
{
	down(t);
	
	if (sum[tr[t][0]]>=k)
	return find(tr[t][0],k);
	else
	if (sum[tr[t][0]]+1==k)
	return t;
	else
	return find(tr[t][1],k-sum[tr[t][0]]-1);
}

int gf(int t)
{
	for (;fa[t];t=fa[t]);
	return t;
}

void dg(int t)
{
	down(t);
	
	if (tr[t][0]) dg(tr[t][0]);
	printf("%d ",t);
	if (tr[t][1]) dg(tr[t][1]);
}

int main()
{
	srand(time(NULL));
	
	scanf("%d%d",&n,&Q);
	fo(i,1,n)
	{
		heap[i]=rand()*32768+rand();
		sum[i]=1;
	}
	fo(i,2,n)
	merge(0,0,gf(i-1),gf(i));
	
	for (;Q;Q--)
	{
		scanf("%d%d",&x,&y);
		
		i=gf(1);
		
		X=find(i,x);
		Fs=find(i,1);
		Ls=find(i,n);
		
		if (x>1)
		split(0,0,gf(Fs),x-1);
		if (y<n)
		split(0,0,gf(X),y-x+1);
		
		rev[gf(X)]^=1;
		
		if (x>1)
		merge(0,0,gf(Fs),gf(X));
		if (y<n)
		merge(0,0,gf(Fs),gf(Ls));
	}
	
	dg(gf(1));
	printf("\n");
}

参考资料

不基于旋转的treap
旋转/非旋转treap的简单操作
可持久化(非旋转式)treap 学习记录
【数据结构】【平衡树】无旋转treap
可持久化(非旋转式)treap 学习记录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值