链接
学习来源以下三篇博客 其中模板主要来自第三篇
学习笔记:平衡树-splay - RagnaLP - 博客园
平衡树详解_LaurenceGen的博客-CSDN博客_平衡树
平衡树概述_lvmaooi的博客-CSDN博客_平衡树是什么
平衡树概述
广义来说,它可以维护一段数列的顺序,不管是treap,splay还是替罪羊,在旋转或者重构的时候都不会改变中序遍历的答案,尽管这个中序遍历不一定是从小到大排列。
Splay概述
它可以把一个节点转到它的任意一个祖先节点上去,而且在旋转的时候遵守某种规则,可以顺带把树变得平衡起来 规则如下
例题
P3369 【模板】普通平衡树
题意
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
- 插入 x 数
- 删除 x 数(若有多个相同的数,因只删除一个)
- 查询 x 数的排名(排名定义为比当前数小的数的个数+1 )
- 查询排名为 x 的数
- 求 x 的前驱(前驱定义为小于 x,且最大的数)
- 求 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;
}