平衡树 之splay专题 例题模板

链接

 学习来源以下三篇博客 其中模板主要来自第三篇

 

学习笔记:平衡树-splay - RagnaLP - 博客园

平衡树详解_LaurenceGen的博客-CSDN博客_平衡树

平衡树概述_lvmaooi的博客-CSDN博客_平衡树是什么

平衡树概述

广义来说,它可以维护一段数列的顺序,不管是treap,splay还是替罪羊,在旋转或者重构的时候都不会改变中序遍历的答案,尽管这个中序遍历不一定是从小到大排列。

Splay概述

它可以把一个节点转到它的任意一个祖先节点上去,而且在旋转的时候遵守某种规则,可以顺带把树变得平衡起来 规则如下

例题

P3369 【模板】普通平衡树

题意

您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x 数
  2. 删除 x 数(若有多个相同的数,因只删除一个)
  3. 查询 x 数的排名(排名定义为比当前数小的数的个数+1 )
  4. 查询排名为 x 的数
  5. 求 x 的前驱(前驱定义为小于 x,且最大的数)
  6. 求 x 的后继(后继定义为大于 x,且最小的数)

我们以这个模板为母模板

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<cstdlib>  
const int INF=1e9;
using namespace std;//重点讲一下splay和dele  
struct Tree{  
    int x,fa,son[2],num,size;  
}b[100005];  
int num;
int root,m,cnt,hack,tot;  
void build(int &u,int l,int r,int las)  
{  
    u=++cnt;  
    int mid=(l+r)/2;  
    b[u].size=r-l+1;  
    b[u].x=mid;  
    b[u].fa=las;  
    if(l<mid) build(b[u].son[0],l,mid-1,u);  
    if(r>mid) build(b[u].son[1],mid+1,r,u);  
}  
inline void update(int u)//更新父节点的size,在旋转时修改会非常蛋疼,不如转完后直接更新  
{  
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+b[u].num;  
}  

inline void rotate(int x,int &k)//旋转操作  
{  
    int y=b[x].fa,z=b[y].fa,l,r;  
    if(b[y].son[0]==x) l=0;else l=1;r=l^1;//考虑一下,是对称的,所以只用写一种情况,然后假装另一种情况也是x为左儿子  
    if(y==k&&k==root) k=x,b[x].fa=0;//如果y是根节点,把根节点赋值为x  
    else//更改爷爷的儿子指向  
    {  
        if(b[z].son[1]==y) b[z].son[1]=x;  
        else b[z].son[0]=x;  
        b[x].fa=z;  
    }  
    b[y].son[l]=b[x].son[r];//画图可知这样两个赋值完成翻转  
    b[x].son[r]=y;  
    b[b[y].son[l]].fa=y;  
    b[b[x].son[r]].fa=x;  
    update(y);  
}  

inline void splay(int x,int &k)  
{  
    int y,z;//y是爸爸,z是儿子  
    while(x!=k)//如果目标点是x,就退出循环  
    {  
        y=b[x].fa;z=b[y].fa;  
        if(y!=k)//如果目标点是爸爸,就只用转x  
        {  
            if((b[z].son[0]==y)^(b[y].son[0]==x)) rotate(x,k);//如果是弯的就转x  
            else rotate(y,k);//直的转y  
        }  
        rotate(x,k);  
    }  
    update(x);//旋转完成后更新x  
}  

inline void insert(int &u,int x,int las)  
{  
    if(u==0)  
    {  
        u=++cnt;  
        b[u].size++;  
        b[u].x=x;  
        b[u].num=1;  
        b[u].fa=las;  
        splay(u,root);  
        return;  
    }  
    b[u].size++;  
    if(x==b[u].x) b[u].num++,splay(u,root);  
    else if(x<b[u].x)  insert(b[u].son[0],x,u);  
    else  insert(b[u].son[1],x,u);  
}  

inline void dele(int u,int x)  
{  
    b[u].size--;  
    if(x==b[u].x)  
    {  
        b[u].num--;  
        splay(u,root);  
        if(b[u].num==0)//如果它被删没了  
        {  
            int lson=b[u].son[0];//左儿子  
            if(lson==0) root=b[u].son[1],b[root].fa=0;//没有左儿子,把根变成右儿子好了  
            else//不如把前驱转到根  
            {  
                while(b[lson].son[1]!=0)lson=b[lson].son[1];  
                splay(lson,root);  
                b[root].son[1]=b[u].son[1];//前面的图可得把要删点的右儿子连在根的右儿子即可。  
                b[b[root].son[1]].fa=root;  
            }  
        }  
        return;  
    }  
    if(x>b[u].x) dele(b[u].son[1],x);  
    else dele(b[u].son[0],x);  
}  

inline int rankth(int u,int x)  
{  
    if(u==0) return 1;  
    if(b[u].x==x)  
    {  
      num=b[u].num;  
      return b[b[u].son[0]].size+1;  
    }  
    if(x>b[u].x) return rankth(b[u].son[1],x)+b[b[u].son[0]].size+b[u].num;  
    if(x<b[u].x) return rankth(b[u].son[0],x);  
}   
inline int findit(int u,int k)
{
	if(k>b[b[u].son[0]].size&&k<=b[u].num+b[b[u].son[0]].size)
	{
		return b[u].x;
	}
	else if(k<=b[b[u].son[0]].size)
	{
		return findit(b[u].son[0],k);
	}
	else return findit(b[u].son[1],k-b[b[u].son[0]].size-b[u].num);
}
int main()  
{  
	//build(root,0,1e5,0);  
    scanf("%d",&m);  
    for(int i=1;i<=m;i++)  
    {  
        int x,q;  
        scanf("%d%d",&x,&q);  
        if(x==1) insert(root,q,0);  
        if(x==2) dele(root,q);  
        if(x==3) printf("%d\n",rankth(root,q));  
        if(x==4) printf("%d\n",findit(root,q));  
        if(x==5)  
        {  
            q=rankth(root,q);  
            printf("%d\n",findit(root,q-1));  
        }  
        if(x==6)  
        {  
            num=0;q=rankth(root,q);  
            printf("%d\n",findit(root,q+num));  
        }  
    }  
}

P3391 【模板】文艺平衡树

题意

您需要写一种数据结构(可参考题目标题),来维护一个有序数列。

其中需要提供以下操作:翻转一个区间,例如原有序序列是 5 4 3 2 1,翻转区间是[2,4] 的话,结果是 5 2 3 4 1。

思路

这个题和上一个不一样的是顺序与序列值无关

主要利用了Splay操作 每次把l-1放在根节点把r+1放在根节点右孩子 那么自己就是根节点右孩子的

左孩子 然后利用lazy标记完成反转操作

注意 多加一个 0 和n+1节点 目的是防止 l-1是0 所以在最后操作时要将l-1 和r+1 整体平移1

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<bits/stdc++.h>  
const int INF=1e9;
using namespace std;//重点讲一下splay和dele  
struct Tree{  
    int x,fa,son[2],num,size; 
	bool tag; 
}b[100005];
int root,m,cnt,hack,tot,n;
void pushdown(int u)
{
	if(b[u].tag==0)return ;
	swap(b[u].son[0],b[u].son[1]);
	b[u].tag=0;
	b[b[u].son[0]].tag^=1;
	b[b[u].son[1]].tag^=1;
}  
void build(int &u,int l,int r,int last)
{
	u=++cnt;
	//cout<<u<<endl;
	int mid=(l+r)/2;
	b[u].fa=last;
	b[u].size=r-l+1;
	b[u].x=mid;
	b[u].num=1;
	if(mid>l)build(b[u].son[0],l,mid-1,u);
	if(mid<r)build(b[u].son[1],mid+1,r,u);
}

inline void update(int u)//更新父节点的size,在旋转时修改会非常蛋疼,不如转完后直接更新  
{  
    b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+b[u].num;  
}  
inline void rotate(int x,int &k)//旋转操作  
{  
    int y=b[x].fa,z=b[y].fa,l,r;  
    if(b[y].son[0]==x) l=0;else l=1;r=l^1;//考虑一下,是对称的,所以只用写一种情况,然后假装另一种情况也是x为左儿子  
    if(y==k&&k==root) k=x,b[x].fa=0;//如果y是根节点,把根节点赋值为x  
    else//更改爷爷的儿子指向  
    {  
        if(b[z].son[1]==y) b[z].son[1]=x;  
        else b[z].son[0]=x;  
        b[x].fa=z;  
    }  
    b[y].son[l]=b[x].son[r];//画图可知这样两个赋值完成翻转  
    b[x].son[r]=y;  
    b[b[y].son[l]].fa=y;  
    b[b[x].son[r]].fa=x;  
    update(y);  
}  


inline void splay(int x,int &k)  
{  
    int y,z;//y是爸爸,z是儿子  
    while(x!=k)//如果目标点是x,就退出循环  
    {  
        y=b[x].fa;z=b[y].fa;  
        if(y!=k)//如果目标点是爸爸,就只用转x  
        {  
            if((b[z].son[0]==y)^(b[y].son[0]==x)) rotate(x,k);//如果是弯的就转x  
            else rotate(y,k);//直的转y  
        }  
        rotate(x,k);  
    }  
    update(x);//旋转完成后更新x  
}   

void findit(int u,int x,int ty)
{
	pushdown(u);
	//cout<<u<<endl;
	if(x==b[b[u].son[0]].size+1)
	{
		if(ty==0)
		{
			splay(u,root);
		}
		else splay(u,b[root].son[1]);
	}
	else if(x<=b[b[u].son[0]].size)
	{
		findit(b[u].son[0],x,ty);
	}
	else 
	{
		findit(b[u].son[1],x-b[b[u].son[0]].size-1,ty);
	}
}
void check(int u)
{
	if(u==0)return ;
	pushdown(u);
	check(b[u].son[0]);
	if(b[u].x!=0&&b[u].x!=n+1)cout<<b[u].x<<" " ;
	
	check(b[u].son[1]); 
}
int main()
{
	cin>>n>>m;
	int l,r;
	build(root,0,n+1,0);
	for(int i=1;i<=m;i++)
	{
		cin>>l>>r;
		findit(root,l,0);
		findit(root,r+1+1,1);
		b[b[b[root].son[1]].son[0]].tag^=1;
	} 
	check(root);
	return 0;
}

Robotic Sort Hdu1890

hdu1890 Robotic Sort(Splay)_w20810的专栏-CSDN博客

题意

n个数排成一列,每次选择序列中的最小值(如果有多个,取原始位置最小的),把它和它前面的所有数翻转,然后把这个数从序列中删去。输出每次选择的最小值的下标。

思路

这和上题相比多了一个 删除 以及区间找最小值 这个最小值我们也在树里定义一个值去维护

每次findmin的rt然后把他splay在root上 然后他的左孩子size+i就是答案

然后再删除这个节点 值得注意的是

pushdown 和pushup操作 一定要及时 在每次修改之后都要加一个 pushup!

代码

#include<bits/stdc++.h>  
const int INF=1e9;
using namespace std;//重点讲一下splay和dele  
const int maxn=1e5+10;
struct Tree{  
    int x,fa,son[2],num,size; 
	bool tag; 
	int minv,now;
}b[100005];
struct node
{
	int pos,v;
}a[maxn];
int root,m,cnt,hack,tot,n;
inline void pushdown(int u)
{
	
	if(u==0||b[u].tag==0)return ;
	swap(b[u].son[0],b[u].son[1]);
	b[u].tag=0;
	b[b[u].son[0]].tag^=1;
	b[b[u].son[1]].tag^=1;
}  
inline void pushup(int u)
{
	if(u==0||b[u].x==0||b[u].x>n)return ;
	b[u].minv=a[b[u].x].v;
	if(b[u].son[0])
	b[u].minv=min(b[u].minv,b[b[u].son[0]].minv);
	
	if(b[u].son[1])
	b[u].minv=min(b[u].minv,b[b[u].son[1]].minv);
	
	b[u].size=b[b[u].son[0]].size+b[b[u].son[1]].size+b[u].num;  
}
inline void build(int &u,int l,int r,int last)
{
	u=++cnt;
	//cout<<u<<endl;
	int mid=(l+r)/2;
	b[u].fa=last;
	b[u].size=r-l+1;
	b[u].x=mid;
	b[u].num=1;
	b[u].minv=a[mid].v;
	b[u].now=a[mid].v;
	b[u].son[0]=0;
	b[u].son[1]=0;
	//cout<<"u:"<<u<<" mid:"<<mid<<" a[mid]:"<<a[mid].v<<" b[u].size:"<<b[u].size<<endl;
	if(mid>l)build(b[u].son[0],l,mid-1,u);
	//b[u].minv=min(b[u].minv,b[b[u].son[0]].minv);
	if(mid<r)build(b[u].son[1],mid+1,r,u);
	//b[u].minv=min(b[u].minv,b[b[u].son[1]].minv);
	pushup(u);
}

inline void rotate(int x,int &k)//旋转操作  
{  
    int y=b[x].fa,z=b[y].fa,l,r;  
    pushdown(z),pushdown(y),pushdown(x);
    if(b[y].son[0]==x) l=0;else l=1;r=l^1;//考虑一下,是对称的,所以只用写一种情况,然后假装另一种情况也是x为左儿子  
    if(y==k&&k==root) k=x,b[x].fa=0;//如果y是根节点,把根节点赋值为x  
    else//更改爷爷的儿子指向  
    {  
        if(b[z].son[1]==y) b[z].son[1]=x;  
        else b[z].son[0]=x;  
        b[x].fa=z;  
    }  
    
    b[y].son[l]=b[x].son[r];//画图可知这样两个赋值完成翻转  
    b[x].son[r]=y;  
    b[b[y].son[l]].fa=y;  
    b[b[x].son[r]].fa=x; 
    
    
    pushup(x);
    pushup(y);
    pushup(z);
}  
inline void splay(int x,int &k)  
{  
    int y=0,z=0;//y是爸爸,z是儿子  
    pushdown(x);
    while(x!=k)//如果目标点是x,就退出循环  
    {  
        y=b[x].fa;
		z=b[y].fa;  
        pushdown(z),pushdown(y);
        if(y!=k)//如果目标点是爸爸,就只用转x  
        {  
            if((b[z].son[0]==y)^(b[y].son[0]==x)) rotate(x,k);//如果是弯的就转x  
            else rotate(y,k);//直的转y  
        }  
        rotate(x,k);  
    }  
	//旋转完成后更新x  
	
    pushup(x);
    pushup(y);
    pushup(z);
    
}
inline int findmin(int u) 
{
	pushdown(u);
	int tmin=b[u].minv;
	int tx=u;
	if(b[u].minv==b[u].now)
	{
		//pushup(u);
		return u;
	}
//	if(b[u].son[1]&&tmin>b[b[u].son[1]].minv)
//	{
//		tx=findmin(b[u].son[1]);
//		tmin=b[b[u].son[1]].minv;
//	}
//	if(b[u].son[0]&&tmin>b[b[u].son[0]].minv)
//	{
//		tx=findmin(b[u].son[0]);
//		//tmin=b[b[u].son[1]].minv;
//	}
	if(b[u].son[1]&&tmin==b[b[u].son[1]].minv)
	{
		//pushup(u);
		tx=findmin(b[u].son[1]);
	}
	else if(b[u].son[0]&&tmin==b[b[u].son[0]].minv)
	{
		//pushup(u);
		tx= findmin(b[u].son[0]);
		//tmin=b[b[u].son[1]].minv;
	}
	//pushup(u);
	return tx;
	
} 
inline void dele(int u,int x)  
{  
    b[u].size--;  
    if(x==b[u].x)  
    {  
        b[u].num--;  
        splay(u,root);  
        pushdown(u);//
        if(b[u].num==0)//如果它被删没了  
        {  
            int lson=b[u].son[0];//左儿子  
            pushdown(lson);//
            if(lson==0) root=b[u].son[1],b[root].fa=0;//没有左儿子,把根变成右儿子好了  
            else//不如把前驱转到根  
            {  
                while(b[lson].son[1]!=0)lson=b[lson].son[1],pushdown(lson);  
                splay(lson,root);  
                b[root].son[1]=b[u].son[1];//前面的图可得把要删点的右儿子连在根的右儿子即可。  
                b[b[root].son[1]].fa=root;  
            }  
        }  
        pushup(root); 
    }  
    else if(x>b[u].x) dele(b[u].son[1],x);  
    else dele(b[u].son[0],x);  
} 

bool cmp1(node a,node b)
{
	if(a.v==b.v)return a.pos<b.pos;
	return a.v<b.v;
}
bool cmp2(node a,node b)
{
	return a.pos<b.pos;
}
inline void init()
{
	cnt=0;
	b[0].num=0;
	b[0].size=0;
}
int main()
{
	
	while(scanf("%d",&n)!=EOF && n!=0)
	{
		if(n==0)break;
		init();
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i].v);
			a[i].pos=i;
		} 
		sort(a+1,a+n+1,cmp1);
		for(int i=1;i<=n;i++)a[i].v=i;
		sort(a+1,a+n+1,cmp2);
		build(root,1,n,0);
		for(int i=1;i<=n;i++)
		{
			int x=findmin(root);
			//cout<<x<<" "<<b[x].x<<endl;
			splay(x,root);
			b[b[root].son[0]].tag^=1;
			printf("%d",b[b[root].son[0]].size+i);
			if(i!=n)printf(" ");
			dele(root,b[root].x);
		} 
		cout<<endl;
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值