树套树浅谈
Posted h-lka
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了树套树浅谈相关的知识,希望对你有一定的参考价值。
今天来说一下线段树套Splay。顺便我也来重新敲一遍模板。
首先,明确一下Splay套线段树用来处理什么问题。它可以支持:插入x,删除x,单点修改,查询x在区间[l,r]的排名,查询区间[l,r]中排名为k的数,以及一个数在区间[l,r]中的前驱,后继。(应该还可以查询区间和等东西,还没写过)
其实它的常数非常大,但是这是树套树中最容易理解的一种。
首先,我们知道,对于一个区间,我们给它建一棵线段树,它的每个节点维护的是区间[l,r]的信息。所以,我们对于每一个节点都建一棵Splay.(听起来就很暴力啊……)
不知道为啥我的指针版Splay套炸了,这里用普通版吧。
首先,我们先来完成最基本的Splay操作。
#include<cstdio> #include<iostream> #include<cstring> #include<string> #define inf (2147483647) using namespace std; const int MAXN=4e6+2; inline void IN(int &x){ int s=0,w=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘){ if(ch==‘-‘)w=-1; ch=getchar(); }while(ch>=48&&ch<=‘9‘){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }x=s*w; } int n,m,a[MAXN],ans,MX; /*-------------------------Splay-------------------------------------*/ int f[MAXN],c[MAXN],s[MAXN],v[MAXN],ch[MAXN][2],rt[MAXN],tot; inline void Splay_del(int x){f[x]=s[x]=c[x]=v[x]=ch[x][1]=ch[x][0]=0;} inline void Splay_pushup(int x){s[x]=(ch[x][0]?s[ch[x][0]]:0)+(ch[x][1]?s[ch[x][1]]:0)+c[x];} inline void Splay_rotate(int x){ int y=f[x],z=f[y],k=ch[y][1]==x,v=ch[x][k^1]; ch[y][k]=v;if(v)f[v]=y;f[x]=z;if(z)ch[z][ch[z][1]==y]=x; f[y]=x;ch[x][k^1]=y;Splay_pushup(y),Splay_pushup(x); }
pushup、rotate和del我就不解释了,根据代码理解吧。
然后我们考虑Splay操作的写法。
实际上并没有什么区别,因为是多颗Splay,所以我们要多传一个当前Splay所在区间的编号,对应地,rt[i]就是原来Splay的rt。
代码:
inline void Splay(int i,int x,int top=0){ while(f[x]!=top){ int y=f[x],z=f[y]; if(z!=top)(ch[z][1]==y)^(ch[y][1]==x)?Splay_rotate(y):Splay_rotate(x); Splay_rotate(x); }if(!top)rt[i]=x; }
接下来,来讲插入。
对于插入,还是一样的流程,往下找,如果是空树,新建节点,赋值;找到值一样的,c[x]++,pushup;没有的话,新建即可。
注意这里也要传区间编号。
inline void Splay_Insert(int i,int x){ int pos=rt[i]; if(!rt[i]){ rt[i]=pos=++tot;v[tot]=x;s[pos]=c[pos]=1; f[pos]=ch[pos][0]=ch[pos][1]=0;return; }int last=0; while(727){ if(v[pos]==x){++c[pos];Splay_pushup(last);break;} last=pos;pos=ch[pos][x>v[pos]]; if(!pos){ pos=++tot;v[pos]=x;s[pos]=c[pos]=1; ch[last][x>v[last]]=pos; f[pos]=last;ch[pos][0]=ch[pos][1]=0; Splay_pushup(last);break; } }Splay(i,pos);return; }
727是生日不要在意……
然后是求某个值在区间[l,r]里的rank.
这个东西,我们把它放到线段树里面理解:如果当前x在区间[l,r]中的一段区间[L,R]中排名为k,在区间[L1,R1]中排名为t,并且这两个区间可以合并成大区间[l,r]的话,那么,当前值x在大区间[l,r]的排名就是k+t.
所以,一个Splay_rank支持查询当前rank,一个Seg函数查询总区间的合并之后的rank。
inline int Splay_rank(int i,int k){ int x=rt[i],cal=0; while(x){ if(v[x]==k)return cal+((ch[x][0])?s[ch[x][0]]:0); else if(v[x]<k){ cal+=((ch[x][0])?s[ch[x][0]]:0)+c[x];x=ch[x][1]; }else x=ch[x][0]; }return cal; } inline void Seg_rank(int x,int l,int r,int L,int R,int Kth){ if(l==L&&r==R){ans+=Splay_rank(x,Kth);return;} if(R<=mid)Seg_rank(lc,l,mid,L,R,Kth); else if(mid<L)Seg_rank(rc,mid+1,r,L,R,Kth); else Seg_rank(lc,l,mid,L,mid,Kth),Seg_rank(rc,mid+1,r,mid+1,R,Kth); }
注意,查询排名的时候,有三种情况,一个是全在左区间,一个是全在右区间,还有横跨两个区间的,不要忘记区分。
Splay找前驱后继就不说了吧……
inline int Splay_Get_pre(int i,int x){ int pos=rt[i];while(pos){ if(v[pos]<x){if(ans<v[pos])ans=v[pos];pos=ch[pos][1];} else pos=ch[pos][0]; }return ans; } inline int Splay_Get_suc(int i,int x){ int pos=rt[i];while(pos){ if(v[pos]>x){if(ans>v[pos])ans=v[pos];pos=ch[pos][0];} else pos=ch[pos][1]; }return ans; }
然后是删除。
先上代码:
inline int Splay_find(int i,int x){ int pos=rt[i];while(x){ if(v[pos]==x){Splay(i,pos);return pos;} pos=ch[pos][x>v[pos]]; }return 0; } inline int Splay_pre(int i){int x=ch[rt[i]][0];while(ch[x][1])x=ch[x][1];return x;} inline int Splay_suc(int i){int x=ch[rt[i]][1];while(ch[x][0])x=ch[x][0];return x;} inline void Splay_Delete(int i,int key){ int x=Splay_find(i,key); if(c[x]>1){--c[x];Splay_pushup(x);return;} if(!ch[x][0]&&!ch[x][1]){Splay_del(rt[i]);rt[i]=0;return;} if(!ch[x][0]){int y=ch[x][1];rt[i]=y;f[y]=0;return;} if(!ch[x][1]){int y=ch[x][0];rt[i]=y;f[y]=0;return;} int p=Splay_pre(i);int lastrt=rt[i]; Splay(i,p,0);ch[rt[i]][1]=ch[lastrt][1];f[ch[lastrt][1]]=rt[i]; Splay_del(lastrt);Splay_pushup(rt[i]); }
先找点,find函数,找完之后判断它是否还存在,如果存在,分类讨论:
左右孩子都没有,直接删掉。
只有右孩子,连接x的右孩子成为根的右孩子
只有左孩子,同理。
如果两个都有,则:
找到区间i的严格小于key的最大数,并记录根。
把这个数旋转到根,然后把之前记录的根的孩子和当前根的孩子连起来,认个爹,然后把之前根删掉,再pushup即可。
改天再补坑……先写作业去……(恨地理
以上是关于树套树浅谈的主要内容,如果未能解决你的问题,请参考以下文章