Splay模板及入门题目

近期学习了Splay数据结构,发现是个挺有趣的东西。对模板题做一些记录~

模板

P3369 【模板】普通平衡树.

#include<cstdio>
#include<cmath>
#include<string>
#include<queue>
#include<map>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

/*
Splay Tree算法:伸展树,自调整形式的二叉平衡树 
*/ 
typedef long long ll;
const int maxn=1e6+1e5+10;
const int inf=1<<30;
const double pi=acos(-1.0);
const int Mod=1e9+7;
const double eps=1e-9;

int root=0,tot=1;                //树根节点 
int fa[maxn], ch[maxn][2];
int val[maxn];           //结点权值和
int cnt[maxn];           //cnt[i]: 与i结点具有相同值的结点个数
int size[maxn];          //左右子树的结点个数(包含值重复) 

//快读快写
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

void update(int x){
//	维护当前子树的结点个数 
	size[x]=size[ch[x][0]]+size[ch[x][1]]+cnt[x];
} 

void rotate(int x){
//  将树的x结点旋转到其父节点位置 
//	y为x父节点,z为y父节点,k表示x是否为y的右儿子 
	int y=fa[x],z=fa[y],k=(ch[y][1]==x);
	ch[z][ch[z][1]==y]=x;
	fa[x]=z;                       //将x与y位置交换,更新z的记录
	
	ch[y][k]=ch[x][k^1]; fa[ch[x][k^1]]=y;    //更新y节点的父亲和儿子
	fa[y]=x;
	
	ch[x][k^1]=y;					 //	更新x的k相对儿子为y 
	update(y); update(x);            //x和y的子树结点个数发生变化,先更新儿子结点y的 
}

void splay(int x, int goal){
//	每次有新节点加入、删除或查询时,都将其旋转至根节点,这样可以保持BST的平衡。
//	将x旋转至成为goal的儿子结点 
	int y,z; 
	while(fa[x]!=goal){
		y=fa[x], z=fa[y];
//		异或为0:x,y,z在共线的链上 
		if(z!=goal) ((ch[y][0]==x)^(ch[z][0]==y))?rotate(x):rotate(y); 
		rotate(x);
	} 
	if(goal==0) root=x;            //0是x的父亲,x为根节点 
} 

void Find(int x){
//	查找x的位置,并将其旋转到根节点; 默认一定存在结点值为x 
	int u=root;
	if(!u) return;          		      //空树

	while(ch[u][x>val[u]]&& x!=val[u])   //儿子节点存在并且当前结点val不是x,才进入到下一层
		u=ch[u][x>val[u]];               //跳转到儿子结点 
	
	splay(u, 0); 
}

void insert(int x){
//	插入值为x的结点 
	int u=root, ff=0;                    //当前结点u,其父节点ff 
	while(u&&x!=val[u]){                 //当前结点存在,且x不等于当前结点的值 
		ff=u; u=ch[u][x>val[u]];         //若x>val[u]搜索右儿子结点,否则搜索左儿子 
	} 
	if(u){                               //存在值为x的结点u 
		cnt[u]++;
	}else{
		u=tot++;                         //增加新的结点编号
		if(ff) ch[ff][x>val[ff]]=u;      //更新其父节点的信息
		ch[u][0]=ch[u][1]=0; fa[u]=ff;
		val[u]=x;
		size[u]=1; cnt[u]=1; 
	} 
//  把当前位置移到根,保证结构的平衡。注意前面因为更改了子树大小,所以这里必须Splay上去进行pushup保证size的正确
	splay(u, 0);                         
} 

int pre(int x){
//	查找前驱结点 
	Find(x);          				//查找后,此时树根即为查询节点
//	x值不在树上,其pre可能为root 
	if(val[root]<x) return root;
	int u=ch[root][0];  			//前驱在当前根结点左子树的最右端 
	if(!u) return -1;
	while(ch[u][1]) u=ch[u][1];     //一直向右走 
	return u; 
} 

int nxt(int x){
//	查找后继结点,同pre()类似 
	Find(x);
//	x不在树上,其nxt可能为root 
	if(val[root]>x) return root;
	int u=ch[root][1];  
	if(!u) return -1;
	while(ch[u][0]) u=ch[u][0];      //一直向左走 
	return u;           
} 

void Delete(int x){
//	删除一个值为x的结点
// 	以x的前驱pre作为根节点,以x的后继nxt作为pre的右儿子;此时x为nxt的左儿子且为叶子结点 
	int xp=pre(x), xn=nxt(x);
	splay(xp, 0); splay(xn, xp);
	int u=ch[xn][0];                 //值为x要被删除的结点 
	if(cnt[u]>1){
		cnt[u]--;
		splay(u, 0);                 //将u旋转到根结点 
	}
	else ch[xn][0]=0;
	
} 

int kth(int k){
//	查找数值排名第k的结点编号 
	int u=root,son;							  
	if(size[u]<k) return -1;                            //当前结点的结点个数小于k 
	while(true){
		son=ch[u][0];                                   //左儿子 
		if(k<=size[son]) u=son;                         //进入左子树 
		else if(k<=size[son]+cnt[u]) return u;    		//排名为k的在当前结点 
		else k-=size[son]+cnt[u], u=ch[u][1];          // 进入右子树 
	}
}

int rank(int x){
	Find(x);                                           //将值为x的结点旋转到根
//	其排名即为左子树的size+1 
	if(val[root]>=x) return size[ch[root][0]]+1;       //若当前根的值>=x,则排名为左子树大小+1 
	else if(val[root]<x) return size[ch[root][0]]+cnt[root]+1;  //若当前根的值<x,则排名为左子树大小+根值的重复次数+1 
} 


int main(){
	int N,m,op,x,lastans=0,res=0;
	N=read(); m=read();
//	预先插入-inf和inf,保证删除最小和最大值时有前驱/后继 
	insert(-inf); insert(inf);
	for(int i=1;i<=N;i++) x=read(), insert(x);
	for(int i=1;i<=m;i++){
		op=read(); x=read();
		x^=lastans;
		if(op==1) insert(x);
		else if(op==2) Delete(x);
		else if(op==3) insert(x), lastans=(rank(x)-1), Delete(x);        //去掉-inf的排名 
		else if(op==4) lastans=val[kth(x+1)];    //不包含-inf的第k名 
		else if(op==5) insert(x),lastans=val[pre(x)],Delete(x);
		else if(op==6) insert(x),lastans=val[nxt(x)],Delete(x);
		if(op>=3){
			res^=lastans;
//			printf("%d\n",lastans);
		}
	} 
//	printf("inf: %d\n",inf);
	printf("%d\n",res);
//	
} 

入门题目

洛谷P3391 文艺平衡树

洛谷P3391 文艺平衡树.
思路:
对于一棵树上连续的区间翻转,可以先令这一区间的结点在同一棵子树上。
翻转区间相当于翻转这棵子树,将其左右儿子交换并递归这一操作。
因此只需要将区间的前驱作为根,区间后继作为其右儿子,该区间就在右儿子的左子树上,
对该区间作一个翻转标记(类似lazy),
当走到这一子树时,下传标记并交换左右子树即可。
区间翻转也有应用于之后的题目。

洛谷P2234 营业额统计

洛谷P2234 营业额统计.
思路:很简单的题目,用stl也可做。
每次插入后查询其最近的前驱和后继,计算和它们差值绝对值最小值,加入res.
注意对于无前驱的情况,只能记录其与后继的差值;
同理无后继也类似处理。

洛谷P2596 [ZJOI2006]书架

P2596 书架.

思路:本质为维护一个排列
对每本书:
pos[x]:编号为x的书在Splay树的编号,val[x]:在Splay树中编号x的书的编号,
树中编号为x的书在书架的位置:size[左儿子]+1。
实现书的编号<->书在树中编号的双射。

1.对Top/Bottom操作:
(1) 将x映射到其在树中的编号x1,将x1作为树根
(2) 若x1无前驱,说明当前已经在书架顶部;若x1无后继,直接交换左右儿子;若都有则将左子树接到后继的左儿子,实现top操作。
对Bottom操作,与top类似。
2.对Insert操作,交换当前结点和前驱/后继即可
3.对Ask操作,找到x在树中编号x1,作为根节点,其左子树的size为其上方书的数量
4.对Query操作,按照kth函数查找到结点编号,输出书的编号即可。

ps: 输入输出不要搞混数值类型!( 输入输出小bug坑了两天

洛谷P1486 [NOI2004]郁闷的出纳员

P1486 郁闷的出纳员.

思路:
对插入的每个工资,维护一个flag记录整体增/减数值,查询时减去flag
I x 插入操作: 若x<Min跳过;否则插入x+flag,记录插入个数.
S x 整体减去x操作:
(1)对flag+=x;
(2)维护splay树,将树中小于临界值Min+flag的工资去掉。具体为先insert(Min+flag),再删除其左子树和当前结点(注意预处理插入-inf)
A x 整体加上x操作: 对flag-=x
F x 查找排名第x大的工资: 利用kth()函数,但先看右子树再看左子树。

ps:
(1)delete后需更新父亲结点
(2)查找第k大需先看右子树再左子树,不能转换为第k小计算

P3224 [HNOI2012]永无乡

P3224 [HNOI2012]永无乡.

思路:
洛谷P3224 永无乡(Splay+并查集) 。
对于合并操作,并查集可以维护每个点所在的集合,
对于集合中数值的排序使用Splay Tree数据结构存储。

算法流程:
(1)合并x和y操作,先并查集查找fx和fy,
若在不在同一集合中,通过fx和fy定位到相应的splay树中,
将size较小的直接逐个结点插入到size较大的,修改f[fy]=fx,完成合并操作。
(2)查询x所在集合的第k大的编号,利用并查集得到fx,
对应到所在的splay树,利用kth函数查找树中第k大的结点编号。

ps:
(1)维护多条splay树,只需要存储每个根节点
(2)注意由编号->树结点编号->值的映射

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值