Splay平衡树入门小结
学习到这部分算是数据结构比较难的部分了,平衡树不好理解代码量大,但在某些情况下确实是不可替代的,所以还是非学不可。
建议先学Treap之后在学Splay,因为其实Splay有不少操作和Treap差不多的。学习了Treap和Splay之后也可以比较两者的差别以及各自的优劣势。
推荐博客:https://www.cnblogs.com/zwfymqz/p/7896036.html 这个大佬说得很好了,认真看了应该就能学会了。
还有https://blog.csdn.net/chenxiaoran666/article/details/81414567
这里讲一下Splay的优势或者说特点,Splay的特别函数是splay操作即伸展操作,其实伸展操作也是基于旋转操作的,它的作用就是把某个点x通过双旋旋转到树根root且没有破坏平衡树的结构,因为双旋的特点可以被证明(这是tarjan大神证明的)这样会使得Splay平衡树的各个操作均摊时间复杂度为O(logn)。只要会用平衡树,其实不用深入了解它的证明过程(只是蒟蒻的观点,毕竟这个很难理解且似乎对做题没什么帮助),但是Splay各个操作函数的原理以及对树带来的影响还是得理解的,这样也方便我们对它进行改造。
首先是模板题:洛谷P3369 普通平衡树
代码几乎是抄上诉博客大佬的,但是大佬的原代码过不了第12个数据,似乎是Rank这个函数没有做Splay操作(不知道是不是这个原因,至少我加了Splay操作就对了)。
#include<bits/stdc++.h>
#define root T[0].ch[1]
using namespace std;
const int N=1e5+;
const int INF=0x3f3f3f3f;
int n,m;
struct node{
int fa,ch[],val,rec,sum; //recÊǸõã´óС£¬sumÊǸõã×ÓÊ÷´óС
}T[N];
int tot=;
void update(int x) { T[x].sum=T[T[x].ch[]].sum+T[T[x].ch[]].sum+T[x].rec; }
int ident(int x) { return T[T[x].fa].ch[]==x?:; } //·µ»ØxÊÇfaµÄÄĸö¶ù×Ó
void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; } //x½Úµã½«³ÉΪfa½ÚµãµÄhowº¢×Ó
void rotate(int x) { //°ÑxÍùÉÏÐýת(°üÀ¨×óÓÒÐý)
int Y=T[x].fa,R=T[Y].fa;
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^],Y,Yson);
connect(Y,x,Yson^);
connect(x,R,Rson);
update(Y); update(x);
}
void splay(int x,int to) { //ÉìÕ¹²Ù×÷£º°ÑxË«Ðýµ½to
to=T[to].fa;
while (T[x].fa!=to) {
int y=T[x].fa;
if (T[y].fa==to) rotate(x);
else if (ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
int newnode(int v,int f) { //н¨½áµãֵΪv£¬faΪf
T[++tot].fa=f;
T[tot].rec=T[tot].sum=;
T[tot].val=v;
return tot;
}
void Insert(int x) { //²åÈëֵΪxµÄ½áµã
int now=root;
if (root==) newnode(x,),root=tot;
else {
while () {
T[now].sum++;
if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
int nxt=x<T[now].val?:; //¸ù¾ÝÖµµÄ´óС¼ÌÐøÍùϲåÈë
if (!T[now].ch[nxt]) { //·¢ÏÖÊǿյ㣬´´½¨Ðµã
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root); return;
}
now=T[now].ch[nxt]; //·ñÔò¼ÌÐøÍùϲåÈë
}
}
}
int find(int x) { //²éÕÒֵΪxµÄλÖò¢·µ»Ø
int now=root;
while () {
if (!now) return ; //ûÓÐÕâ¸öµã
if (T[now].val==x) { splay(now,root); return now; } //ÕÒµ½ÁË
int nxt=x<T[now].val?:; //¸ù¾ÝÖµ¼ÌÐøÍùÏÂÕÒ
now=T[now].ch[nxt];
}
}
void Delete(int x) { //ɾ³ýֵΪxµÄµã
int pos=find(x); //ÏÈÕÒµ½Õâ¸öµãµÄλÖÃ
if (!pos) return; //ûÓÐÕâ¸öµã
if (T[pos].rec>) { T[pos].rec--; T[pos].sum--; return; } //Õâ¸öµã¶àÓÚÒ»¸ö£ºÉ¾³ýÁ˲»»á³öÏÖ¿Õȱ
else {
if (!T[pos].ch[] && !T[pos].ch[]) {root=; return;} //ɾ³ýÁ˱ä¿ÕÊ÷
else if (!T[pos].ch[]) {root=T[pos].ch[]; T[root].fa=; return;} //ûÓÐ×ó¶ù×Ó£¬ÓÒ¶ù×ÓÖ±½ÓÉÏλ
else { //ûÓÐÓÒ¶ù×Ó£ºÔÚËüµÄ×ó¶ù×ÓÖÐÕÒµ½×î´óµÄ£¬Ðýתµ½¸ù£¬°ÑËüµÄÓÒ¶ù×Óµ±×ö¸ù(Ò²¾ÍÊÇËü×î´óµÄ×ó¶ù×Ó)µÄÓÒ¶ù×Ó
int left=T[pos].ch[];
while (T[left].ch[]) left=T[left].ch[];
splay(left,T[pos].ch[]);
connect(T[pos].ch[],left,);
connect(left,,);
update(left);
}
}
}
int Rank(int x) { //·µ»ØֵΪxµÄ×îСÅÅÃû
int now=root,ans=; //ansÊÇÒ»±ß×ßÒ»±ßÀÛ¼Ó´ð°¸
while () {
if (T[now].val==x) {ans+=T[T[now].ch[]].sum; splay(now,root); return ans+;} //ÕÒµ½·µ»Ø
int nxt=x<T[now].val?:;
if (nxt==) ans+=T[T[now].ch[]].sum+T[now].rec; //ÏÂÒ»²½ÍùÓÒÀÛ¼Ó´ð°¸
now=T[now].ch[nxt];
}
}
int Kth(int x) { //·µ»ØÏÖÔÚµÚxСµÄÊýÖµ
int now=root;
while () {
int used=T[now].sum-T[T[now].ch[]].sum; //×ó×ÓÊ÷¼°xµã×Ü´óС
if (T[T[now].ch[]].sum<x && x<=used) {splay(now,root); return T[now].val; } //¼ÐÔÚÖм䣺´ð°¸¾ÍÊǸõã
if (x<used) now=T[now].ch[]; //ÔÚ×ó±ß
else now=T[now].ch[],x-=used; //ÔÚÓÒ±ß
}
}
int lower(int x) { //Ñ°ÕÒxµÄÇ°ÇýÖµ£ºÐ¡ÓÚxÇÒ×î´óµÄÖµ
int now=root,ans=-INF;
while (now) {
if (T[now].val<x) ans=max(ans,T[now].val); //Ò»±ßÕÒ×î´óµÄ
int nxt=x<=T[now].val?:; //Ò»±ßÏòÏÂ×ß
now=T[now].ch[nxt];
}
return ans;
}
int upper(int x) { //Ñ°ÕÒxµÄºó¼ÌÖµ£º´óÓÚxÇÒ×îСµÄÖµ
int now=root,ans=INF;
while (now) {
if (T[now].val>x) ans=min(ans,T[now].val); //Ò»±ßÕÒ×îСµÄ
int nxt=x<T[now].val?:; //Ò»±ßÏòÏÂ×ß
now=T[now].ch[nxt];
}
return ans;
} int main()
{
cin>>n;
for (int i=;i<=n;i++) {
int opt,x; scanf("%d%d",&opt,&x);
if (opt==) Insert(x);
if (opt==) Delete(x);
if (opt==) printf("%d\n",Rank(x));
if (opt==) printf("%d\n",Kth(x));
if (opt==) printf("%d\n",lower(x));
if (opt==) printf("%d\n",upper(x));
}
return ;
}
洛谷P2234[HNOI]营业额统计
这题就是每个数在前面的数找前驱和后缀更靠近的哪一个。Splay裸题。
#include<bits/stdc++.h>
#define root T[0].ch[1]
using namespace std;
const int N=1e5+;
const int INF=0x3f3f3f3f;
int n,m;
struct node{
int fa,ch[],val,rec,sum; //rec是该点大小,sum是该点子树大小
}T[N];
int tot=;
void update(int x) { T[x].sum=T[T[x].ch[]].sum+T[T[x].ch[]].sum+T[x].rec; }
int ident(int x) { return T[T[x].fa].ch[]==x?:; } //返回x是fa的哪个儿子
void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; } //x节点将成为fa节点的how孩子
void rotate(int x) { //把x往上旋转(包括左右旋)
int Y=T[x].fa,R=T[Y].fa;
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^],Y,Yson);
connect(Y,x,Yson^);
connect(x,R,Rson);
update(Y); update(x);
}
void splay(int x,int to) { //伸展操作:把x双旋到to
to=T[to].fa;
while (T[x].fa!=to) {
int y=T[x].fa;
if (T[y].fa==to) rotate(x);
else if (ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
int newnode(int v,int f) { //新建结点值为v,fa为f
T[++tot].fa=f;
T[tot].rec=T[tot].sum=;
T[tot].val=v;
return tot;
}
void Insert(int x) { //插入值为x的结点
int now=root;
if (root==) newnode(x,),root=tot;
else {
while () {
T[now].sum++;
if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
int nxt=x<T[now].val?:; //根据值的大小继续往下插入
if (!T[now].ch[nxt]) { //发现是空点,创建新点
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root); return;
}
now=T[now].ch[nxt]; //否则继续往下插入
}
}
}
int find(int x) { //查找值为x的位置并返回
int now=root;
while () {
if (!now) return ; //没有这个点
if (T[now].val==x) { splay(now,root); return now; } //找到了
int nxt=x<T[now].val?:; //根据值继续往下找
now=T[now].ch[nxt];
}
}
void Delete(int x) { //删除值为x的点
int pos=find(x); //先找到这个点的位置
if (!pos) return; //没有这个点
if (T[pos].rec>) { T[pos].rec--; T[pos].sum--; return; } //这个点多于一个:删除了不会出现空缺
else {
if (!T[pos].ch[] && !T[pos].ch[]) {root=; return;} //删除了变空树
else if (!T[pos].ch[]) {root=T[pos].ch[]; T[root].fa=; return;} //没有左儿子,右儿子直接上位
else { //没有右儿子:在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子
int left=T[pos].ch[];
while (T[left].ch[]) left=T[left].ch[];
splay(left,T[pos].ch[]);
connect(T[pos].ch[],left,);
connect(left,,);
update(left);
}
}
}
int Rank(int x) { //返回值为x的最小排名
int now=root,ans=; //ans是一边走一边累加答案
while () {
if (T[now].val==x) {ans+=T[T[now].ch[]].sum; splay(now,root); return ans+;} //找到返回
int nxt=x<T[now].val?:;
if (nxt==) ans+=T[T[now].ch[]].sum+T[now].rec; //下一步往右累加答案
now=T[now].ch[nxt];
}
}
int Kth(int x) { //返回现在第x小的数值
int now=root;
while () {
int used=T[now].sum-T[T[now].ch[]].sum; //左子树及x点总大小
if (T[T[now].ch[]].sum<x && x<=used) {splay(now,root); return T[now].val; } //夹在中间:答案就是该点
if (x<used) now=T[now].ch[]; //在左边
else now=T[now].ch[],x-=used; //在右边
}
}
int lower(int x) { //寻找x的前驱值:小于x且最大的值
int now=root,ans=-INF;
while (now) {
if (T[now].val<x) ans=max(ans,T[now].val); //一边找最大的
int nxt=x<=T[now].val?:; //一边向下走
now=T[now].ch[nxt];
}
return ans;
}
int upper(int x) { //寻找x的后继值:大于x且最小的值
int now=root,ans=INF;
while (now) {
if (T[now].val>x) ans=min(ans,T[now].val); //一边找最小的
int nxt=x<T[now].val?:; //一边向下走
now=T[now].ch[nxt];
}
return ans;
} int main()
{
cin>>n;
long long ans=;
for (int i=;i<=n;i++) {
int x; scanf("%d",&x);
if (i==) ans+=x;
else if (find(x)) ans+=;
else ans+=min(x-lower(x),upper(x)-x);
Insert(x);
}
cout<<ans<<endl;
return ;
}
洛谷2286[HNOI]宠物收留场
宠物有冗余的时候把宠物插到平衡树上,人有冗余的时候把人插到平衡树上,分清这个逻辑之后就是套板子的事。
#include<bits/stdc++.h>
#define root T[0].ch[1]
using namespace std;
const int N=1e5+;
const int MOD=1e6;
const int INF=0x3f3f3f3f;
int n,m;
struct node{
int fa,ch[],val,rec,sum; //rec是该点大小,sum是该点子树大小
}T[N];
int tot=;
void update(int x) { T[x].sum=T[T[x].ch[]].sum+T[T[x].ch[]].sum+T[x].rec; }
int ident(int x) { return T[T[x].fa].ch[]==x?:; } //返回x是fa的哪个儿子
void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; } //x节点将成为fa节点的how孩子
void rotate(int x) { //把x往上旋转(包括左右旋)
int Y=T[x].fa,R=T[Y].fa;
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^],Y,Yson);
connect(Y,x,Yson^);
connect(x,R,Rson);
update(Y); update(x);
}
void splay(int x,int to) { //伸展操作:把x双旋到to
to=T[to].fa;
while (T[x].fa!=to) {
int y=T[x].fa;
if (T[y].fa==to) rotate(x);
else if (ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
int newnode(int v,int f) { //新建结点值为v,fa为f
T[++tot].fa=f;
T[tot].rec=T[tot].sum=;
T[tot].val=v;
return tot;
}
void Insert(int x) { //插入值为x的结点
int now=root;
if (root==) newnode(x,),root=tot;
else {
while () {
T[now].sum++;
if (T[now].val==x) { T[now].rec++; splay(now,root); return; }
int nxt=x<T[now].val?:; //根据值的大小继续往下插入
if (!T[now].ch[nxt]) { //发现是空点,创建新点
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root); return;
}
now=T[now].ch[nxt]; //否则继续往下插入
}
}
}
int find(int x) { //查找值为x的位置并返回
int now=root;
while () {
if (!now) return ; //没有这个点
if (T[now].val==x) { splay(now,root); return now; } //找到了
int nxt=x<T[now].val?:; //根据值继续往下找
now=T[now].ch[nxt];
}
}
void Delete(int x) { //删除值为x的点
int pos=find(x); //先找到这个点的位置
if (!pos) return; //没有这个点
if (T[pos].rec>) { T[pos].rec--; T[pos].sum--; return; } //这个点多于一个:删除了不会出现空缺
else {
if (!T[pos].ch[] && !T[pos].ch[]) {root=; return;} //删除了变空树
else if (!T[pos].ch[]) {root=T[pos].ch[]; T[root].fa=; return;} //没有左儿子,右儿子直接上位
else { //没有右儿子:在它的左儿子中找到最大的,旋转到根,把它的右儿子当做根(也就是它最大的左儿子)的右儿子
int left=T[pos].ch[];
while (T[left].ch[]) left=T[left].ch[];
splay(left,T[pos].ch[]);
connect(T[pos].ch[],left,);
connect(left,,);
update(left);
}
}
}
int Rank(int x) { //返回值为x的最小排名
int now=root,ans=; //ans是一边走一边累加答案
while () {
if (T[now].val==x) {ans+=T[T[now].ch[]].sum; splay(now,root); return ans+;} //找到返回
int nxt=x<T[now].val?:;
if (nxt==) ans+=T[T[now].ch[]].sum+T[now].rec; //下一步往右累加答案
now=T[now].ch[nxt];
}
}
int Kth(int x) { //返回现在第x小的数值
int now=root;
while () {
int used=T[now].sum-T[T[now].ch[]].sum; //左子树及x点总大小
if (T[T[now].ch[]].sum<x && x<=used) {splay(now,root); return T[now].val; } //夹在中间:答案就是该点
if (x<used) now=T[now].ch[]; //在左边
else now=T[now].ch[],x-=used; //在右边
}
}
int lower(int x) { //寻找x的前驱值:小于x且最大的值
int now=root,ans=-INF;
while (now) {
if (T[now].val<x) ans=max(ans,T[now].val); //一边找最大的
int nxt=x<=T[now].val?:; //一边向下走
now=T[now].ch[nxt];
}
return ans;
}
int upper(int x) { //寻找x的后继值:大于x且最小的值
int now=root,ans=INF;
while (now) {
if (T[now].val>x) ans=min(ans,T[now].val); //一边找最小的
int nxt=x<T[now].val?:; //一边向下走
now=T[now].ch[nxt];
}
return ans;
} int getit(int x) {
int t1=lower(x),t2=upper(x);
if (x-t1<=t2-x) {
Delete(t1);
return x-t1;
} else {
Delete(t2);
return t2-x;
}
} int main()
{
cin>>n;
int pet=,pop=,ans=;
for (int i=;i<=n;i++) {
int opt,x; scanf("%d%d",&opt,&x);
if (opt==) { //pet
if (pop==) Insert(x),pet++;
else ans+=getit(x),pop--;
} else { //people
if (pet==) Insert(x),pop++;
else ans+=getit(x),pet--;
}
ans%=MOD;
}
cout<<ans<<endl;
return ;
}
Splay被称为“序列之王”,在区间问题上的应用十分广泛灵活。同等地,Splay不那么容易理解而且代码复杂容易写错常数大,必须要多多练习才能熟练应用。
入门Splay区间题:洛谷P3391文艺平衡树
首先按照位置即下标作为关键字建立平衡树,对于需要旋转的一个区间[l,r],把l-1旋转到根节点,把r+1旋转到根节点的右儿子,那么根据平衡树的特点,此时根节点的右儿子的左子树比r+1小比l-1大就肯定是区间[l,r],所以把根节点的右儿子的左子树打上lazy-tag翻转标记,像线段树那样向下传递即可。
#include<bits/stdc++.h>
#define root T[0].ch[1]
using namespace std;
const int N=1e5+;
const int INF=0x3f3f3f3f;
int n,m;
struct node{
int fa,ch[],val,rec,sum; //rec是该点大小,sum是该点子树大小
int tag;
}T[N];
int tot=;
void update(int x) { T[x].sum=T[T[x].ch[]].sum+T[T[x].ch[]].sum+T[x].rec; }
int ident(int x) { return T[T[x].fa].ch[]==x?:; } //返回x是fa的哪个儿子
void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; } //x节点将成为fa节点的how孩子
void rotate(int x) { //把x往上旋转(包括左右旋)
int Y=T[x].fa,R=T[Y].fa;
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^],Y,Yson);
connect(Y,x,Yson^);
connect(x,R,Rson);
update(Y); update(x);
}
void splay(int x,int to) { //伸展操作:把x双旋到to
to=T[to].fa;
while (T[x].fa!=to) {
int y=T[x].fa;
if (T[y].fa==to) rotate(x);
else if (ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
}
void pushdown(int x) { //向下传递标记
if (T[x].tag) {
swap(T[x].ch[],T[x].ch[]);
T[T[x].ch[]].tag^=;
T[T[x].ch[]].tag^=;
T[x].tag=;
}
} int getval(int x) { //返回中序遍历为x的所在结点
int now=root;
pushdown(now); //记得把标记向下传递
while (x!=T[T[now].ch[]].sum+) {
if (T[T[now].ch[]].sum<x) x-=T[T[now].ch[]].sum+T[now].rec,now=T[now].ch[];
else now=T[now].ch[];
pushdown(now); //记得把标记向下传递
}
return now;
} int build(int l,int r) { //以下标[l,r]为关键字建树
if (l>r) return ;
int mid=(l+r)>>;
connect(build(l,mid-),mid,); //左子树练到父亲mid
connect(build(mid+,r),mid,); //右子树连到父亲mid
T[mid].val=mid;
T[mid].rec=; //mid这个点的大小
T[mid].tag=; //刚开始没有翻转标记
update(mid);
return mid;
} void rever(int x,int y) { //把位置区间[x,y]翻转
int l=getval(x-),r=getval(y+); //找到翻转区间两端的结点
splay(l,root); //l旋到树根
splay(r,T[root].ch[]); //把r旋到树根右儿子
T[T[T[root].ch[]].ch[]].tag^=; //此时根节点的右儿子的左儿子所代表的就是区间l,r
} int main()
{
cin>>n>>m;
root=build(,n+); //用2-n+1代表1-n的元素
for (int i=;i<=m;i++) {
int x,y; scanf("%d%d",&x,&y);
rever(x+,y+);
}
for (int i=;i<=n;i++)
printf("%d ",T[getval(i+)].val-);
return ;
}
洛谷P3165[CQOI2014]排序机械臂
这道题非常有意思。使我对平衡树的理解更加深了,首先得明白平衡树一旦建树的时候结构确定了,经过旋转伸展等操作是不会改变它的平衡性的(亦即是这棵树的中序遍历就不会因为旋转伸展操作改变)。而且对于平衡树来说重要的是中序遍历的顺序是确定的有大小平衡关系的,但是中序遍历的编号并不是要求有大小关系的,就是 中序遍历≠中序遍历编号,平衡树结点的编号是可以随便定的,只要这些编号的相对顺序确定那么中序遍历的顺序就确定了 。(这一点或许大神一下子就明白了,但是确实花了蒟蒻一些时间去理解)。
说回这一题,我们很容易想到一个显然的做法:按顺序找最小的,次小的,第三小的......然后依次翻转区间,也能显然发现这个做法的难点在于这里的区间是是会变化的,我们根本不知道最小的位置在哪,次小的位置在哪?。。。。。。
看回我们第一段的结论,平衡树的编号是可以随便定的,那么我们就直接让最小的结点编号为1,次小的编号为2,以此类推。然后注意建树的时候还是得按照原数组相对顺序建树(这是区间翻转的前提),但是注意这时我们将要做的翻转操作和结点编号是一一对应的!那么此时我们只要知道结点1在区间的位置(这也是该点在平衡树中序遍历的位置,也是该点的名次),然后翻转即可。问题就变成怎么找到这个点的名次?答案是把这个点splay到树根,那么树根左子树Size加一就是它的名次。于是此题可解!
这题还有一些小细节,例如因为输出答案的时候有可能没有下传标记所以splay操作的时候要顺便下传标记。具体可以看代码:
#include<bits/stdc++.h>
#define root T[0].ch[1]
using namespace std;
const int N=1e5+;
const int INF=0x3f3f3f3f;
int n,m,b[N];
struct node{
int fa,ch[],val,rec,sum; //rec是该点大小,sum是该点子树大小
int tag;
}T[N];
struct dat{
int val,pos;
bool operator < (const dat &rhs) const {
return val<rhs.val || val==rhs.val && pos<rhs.pos;
}
}a[N];
int tot=;
void update(int x) { T[x].sum=T[T[x].ch[]].sum+T[T[x].ch[]].sum+T[x].rec; }
int ident(int x) { return T[T[x].fa].ch[]==x?:; } //返回x是fa的哪个儿子
void connect(int x,int fa,int how) { T[fa].ch[how]=x; T[x].fa=fa; } //x节点将成为fa节点的how孩子
void pushdown(int x) { //向下传递标记
if (x && T[x].tag) { //这里需要注意:0是一个虚点,这个点一切信息都是不能用的,除了恰好用T[0].ch[1]=root,这是因为root的fa一定是T[0]
swap(T[x].ch[],T[x].ch[]);
T[T[x].ch[]].tag^=;
T[T[x].ch[]].tag^=;
T[x].tag=;
}
}
void rotate(int x) { //把x往上旋转(包括左右旋)
int Y=T[x].fa,R=T[Y].fa;
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^],Y,Yson);
connect(Y,x,Yson^);
connect(x,R,Rson);
update(Y); update(x);
}
void splay(int x,int to) { //伸展操作:把x双旋到to
to=T[to].fa;
while (T[x].fa!=to) {
int y=T[x].fa;
pushdown(T[y].fa); pushdown(y); pushdown(x); //把标记往下传递
if (T[y].fa==to) rotate(x);
else if (ident(x)==ident(y)) rotate(y),rotate(x);
else rotate(x),rotate(x);
}
} int getval(int x) { //返回中序遍历为x的所在结点
int now=root;
pushdown(now); //记得把标记向下传递
while (x!=T[T[now].ch[]].sum+) {
if (T[T[now].ch[]].sum<x) x-=T[T[now].ch[]].sum+T[now].rec,now=T[now].ch[];
else now=T[now].ch[];
pushdown(now); //记得把标记向下传递
}
return now;
} int build(int l,int r) { //以下标[l,r]为关键字建树
if (l>r) return ;
int mid=(l+r)>>;
int nowid=b[mid];
if (mid>l) connect(build(l,mid-),nowid,); //左子树练到父亲nowid
if (r>mid) connect(build(mid+,r),nowid,); //右子树连到父亲nowid
T[nowid].rec=; //mid这个点的大小
T[nowid].tag=; //刚开始没有翻转标记
update(nowid);
return nowid;
} void rever(int x,int y) { //把位置(在平衡树上就是中序遍历)区间[x,y]翻转
int l=getval(x-),r=getval(y+); //找到翻转区间两端的结点
splay(l,root); //l旋到树根
splay(r,T[root].ch[]); //把r旋到树根右儿子
T[T[T[root].ch[]].ch[]].tag^=; //此时根节点的右儿子的左儿子所代表的就是区间l,r
} int main()
{
cin>>n;
for (int i=;i<=n;i++) scanf("%d",&a[i].val),a[i].pos=i;
sort(a+,a+n+);
//这里的b数组是理解平衡树的关键:我们仍然是以位置为关键字建树(即排序还是按照位置大小)
//但是结点编号是b数组确定的,需要注意的是尽管编号是乱序的但并不影响这棵树还是以位置为关键字平衡的,只是这棵树上挂的点编号不按顺序
for (int i=;i<=n;i++) b[a[i].pos+]=i+;
b[]=; b[n+]=n+; root=build(,n+); //用2-n+1代表1-n的元素
for (int i=;i<=n;i++) {
splay(b[a[i].pos+],root);
int rnk=T[T[root].ch[]].sum+T[root].rec;
rever(i+,rnk);
printf("%d ",rnk-);
}
return ;
}
Splay平衡树入门小结的更多相关文章
- splay树入门(带3个例题)
splay树入门(带3个例题) 首先声明,本教程的对象是完全没有接触过splay的OIer,大牛请右上角.. PS:若代码有误,请尽快与本人联系,我会尽快改正 首先引入一下splay的概念,他的中文名 ...
- BST,Splay平衡树学习笔记
BST,Splay平衡树学习笔记 1.二叉查找树BST BST是一种二叉树形结构,其特点就在于:每一个非叶子结点的值都大于他的左子树中的任意一个值,并都小于他的右子树中的任意一个值. 2.BST的用处 ...
- mybatis入门小结(六)
入门小结---查询 1.1.1.1.1 #{}和${} #{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以 ...
- 【机器学习】Learning to Rank入门小结 + 漫谈
Learning to Rank入门小结 + 漫谈 Learning to Rank入门小结 Table of Contents 1 前言 2 LTR流程 3 训练数据的获取4 特征抽取 3.1 人工 ...
- 关于二叉查找树的一些事儿(bst详解,平衡树入门)
最近刚学了平衡树,然后突发奇想写几篇博客纪念一下,可能由于是刚学的缘故,还有点儿生疏,望大家海涵 说到平衡树,就不得不从基础说起,而基础,正是二叉查找树 什么是二叉查找树?? 大家观察一下下面的这棵二 ...
- JZYZOJ1998 [bzoj3223] 文艺平衡树 splay 平衡树
http://172.20.6.3/Problem_Show.asp?id=1998 平衡树区间翻转的板子,重新写一遍,给自己码一个板子. #include<iostream> #incl ...
- poj3481(splay tree 入门题)
平衡树都能做. // // main.cpp // splay // // Created by 陈加寿 on 16/3/25. // Copyright © 2016年 chenhuan001. A ...
- Luogu P3391 【模板】文艺平衡树 Splay 平衡树
https://www.luogu.org/problemnew/show/P3391 以前写过题解的入门题重写练板子.wdnmd真就 ' == ' 写成 ' = ' 了编译器不报错呗. #inclu ...
- 来自Java程序员的Python新手入门小结
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
随机推荐
- -bash: findstr: command not found 问题解决
今天有个任务,需要获取apk的版本信息,百度之后说是之下下面的命令就行 adb shell dumpsys package com.baidu.searchbox | findstr versionC ...
- 安装VueCli-3.0
vue-cli 3.0 安装1 vue-cli 3.0 安装/卸载 npm install -g @vue/cli npm uninstall @vue/cli -g vue --version 查看 ...
- list列表切片方法汇总
python为list列表提供了强大的切片功能,下面是一些常见功能的汇总 """ 使用模式: [start:end:step] 其中start表示切片开始的位置,默认是0 ...
- 有关于log4j详解
Log4j记录日志使用方法 一.什么是log4j Log4J是Apache的一个开放源代码的项目.通过使用Log4J,程序员可以控制日志信息输送的目的地,包括控制台,文件,GUI组件和NT事件记录器, ...
- [CSP-S模拟测试]:Travel(贪心+构造)
题目描述 给定一个长度为$n$的格子序列$x_1,x_2,...,x_n$.每一次$Lyra$可以选择向左跳到任意一个还没到过的位置,也可以向右跳到任意一个还没到过的位置.如果现在$Lyra$在格子$ ...
- Java 封装 继承 多态
Java 继承 继承的概念 继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类. 继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法 ...
- composer proc_open(): fork failed – Cannot allocate memory
一般小的VPS 才1G内存,如果使用composer会提示内存不足的现象 解决办法,可以使用交换内存 直接命令 /bin/dd if=/dev/zero of=/var/swap.1 bs=1M co ...
- save change is not permitted
https://support.microsoft.com/en-my/help/956176/error-message-when-you-try-to-save-a-table-in-sql-se ...
- p4899 [IOI2018] werewolf 狼人
分析 我用的主席树维护qwq 代码 #include<iostream> #include<cstdio> #include<cstring> #include&l ...
- 牛客提高D3t1 破碎的矩阵
分析 我们发现如果行的异或和等于列的异或和那么对于n-1行m-1列的所有数的选择都是任意的 因为一定可以在它的行末/列末选一个合适的数是的整体满足 但是我们发现对于右下角那一个数是否满足存疑 我们设矩 ...