平衡樹實際很簡單的
我不會帶指針的Splay,因此我就寫非指針型的Splay
Splay是基於二叉查找樹(bst)實現的
什麼是二叉查找樹呢?就是一棵樹唄,可是這棵樹知足性質:一個節點的左孩子必定比它小,右孩子必定比它大
好比:
![1101696-20171125191307484-1881794250.png](http://static.javashuo.com/static/loading.gif)
這就是一棵最基本二叉查找樹
對於每次插入,它的指望複雜度大約是log^2 n級別的,可是存在極端狀況,好比9999999 9999998 9999997.....1這種數據,會直接被卡成n^2級別
在這種狀況下,平衡樹出現了!
1.定義Splay
struct node
{
int v;//權值
int fa;//父親節點
int ch[2];//0表明左兒子,1表明右兒子
int rec;//這個權值的節點出現的次數
int sum;//子節點的數量
}tree[N];//N爲節點最多有多少
int tot;//tot表示不算重複的有多少節點
2.Splay的核心
Rotate
首先考慮一下,咱們要把一個點挪到根,那咱們首先要知道怎麼讓一個點挪到它的父節點
狀況1:
當X是Y的左孩子時
![1101696-20171125194733656-1618296206.png](http://static.javashuo.com/static/loading.gif)
ABC實際能夠是子樹,但這裏假設ABC都是點
這時候若是咱們讓X成爲Y的父親,只會影響到3組點的關係
B與X,X與Y,X與R
根據二叉排序樹的性質
B會成爲Y的左兒子
Y會成爲X的右兒子
X會成爲R的兒子,具體是什麼兒子,這個要看Y是R的啥兒子
![1101696-20171125200053250-822796586.png](http://static.javashuo.com/static/loading.gif)
狀況2:
當X是Y的右孩子
本質上是和狀況1同樣的qaq
![1101696-20171125200211625-2022743205.png](http://static.javashuo.com/static/loading.gif)
旋轉後變成
![1101696-20171125200354937-579910866.png](http://static.javashuo.com/static/loading.gif)
能不能把這兩種狀況合併呢qaq?結果是確定的
咱們須要一個函數來肯定這個節點是他父節點的左孩子仍是右孩子
inline bool findd(register int x)
{
return tree[tree[x].fa].ch[0]==x?0:1;
}
若是是左孩子的話會返回0,右孩子會返回1
那麼咱們不可貴到R,Y,X這三個節點的信息
int Y=tree[x].fa;
int R=tree[Y].fa;
int Yson=findd(x);
int Rson=findd(Y);
B的狀況咱們能夠根據X的狀況推算出來,根據^運算的性質,0^1=1,1^1=0,2^1=3,3^1=2,並且B相對於X的位置必定是與X相對於Y的位置是相反的
(不然在旋轉的過程當中不會對B產生影響)
int B=tree[x].ch[Yson^1];
而後咱們考慮鏈接的過程
根據上面的圖,不可貴到(本身向上翻qaq)
1.B成爲Y的哪一個兒子與X是Y的哪一個兒子是同樣的
2.Y成爲X的哪一個兒子與X是Y的哪一個兒子相反
3.X成爲R的哪一個兒子與Y是R的哪一個兒子相同
connect(B,Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
connect函數也很好寫
inline void connect(register int x,register int fa,register int son) //把x轉爲fa的son(son是0/1,表示左孩子或右孩子)
{
tree[x].fa=fa;
tree[fa].ch[son]=x;
}
Rotate代碼總覽(旋轉完不要忘了update):
inline void update(register int x)
{
tree[x].sum=tree[tree[x].ch[0]].sum+tree[tree[x].ch[1]].sum+tree[x].rec;
}
inline bool findd(register int x)
{
return tree[tree[x].fa].ch[0]==x?0:1;
}
inline void connect(register int x,register int fa,register int son) //把x轉爲fa的son(son是0/1,表示左孩子或右孩子)
{
tree[x].fa=fa;
tree[fa].ch[son]=x;
}
inline void rotate(register int x)
{
int Y=tree[x].fa;
int R=tree[Y].fa;
int Yson=findd(x);
int Rson=findd(Y);
int B=tree[x].ch[Yson^1];
connect(B,Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y),update(x);
}
Splay
Splay(x,to)是實現把x節點搬到to節點
最簡單的辦法,對於x這個節點,每次上旋直到to
可是!
毒瘤的出題人能夠構造數據把上面的這種方法卡到n^2 qaq*
下面咱們介紹一下雙旋的Splay
這裏的狀況有不少,可是總的來講就三種狀況
1.to是x的爸爸,
這樣的話吧x旋轉上去就好
if(tree[tree[x].fa].fa==to)
rotate(x);
2.x和他爸爸和他爸爸的爸爸在一條線上(文字遊戲)
其實就是findd(x)=findd(tree[x].fa)
這時候先把Y旋轉上去,再把X旋轉上去就好
if(findd(x)==find(tree[x].fa))
rotate(tree[x].fa),rotate(x);
3.x和他爸爸和他爸爸的爸爸不在一條線上(和2相反)
這時候把X旋轉兩次就好
spaly函數的代碼:
inline void splay(register int x,register int to)
{
to=tree[to].fa;
while(tree[x].fa!=to)
{
int y=tree[x].fa;
if(tree[y].fa==to)
rotate(x);
else if(findd(x)==findd(y))
rotate(y),rotate(x);
else
rotate(x),rotate(x);
}
}
Splay的核心代碼到此結束
剩下的就是一些其餘的東西(雖然說有的也挺重要qaq)
3.其餘的一些函數
insert
根據前面講的,咱們在插入一個數以後,須要將其旋轉到根
首先,當這棵樹已經沒有節點的時候,咱們直接新建一個節點就好
inline int newpoint(register int v,register int fa)
{
tree[++tot].fa=fa;
tree[tot].v=v;
tree[tot].sum=tree[tot].rec=1;
return tot;
}
而後,當這可樹有節點的時候,咱們根據二叉查找樹的性質,不斷向下走,直到找到一個能夠插入的點,注意在走的時候須要更新一個每一個節點的sum值
inline void Insert(register int x)
{
int now=tree[0].ch[1];
if(tree[0].ch[1]==0)
{
newpoint(x,0);
tree[0].ch[1]=tot;
}
else
{
while(19260817)
{
++tree[now].sum;
if(tree[now].v==x)
{
++tree[now].rec;
splay(now,tree[0].ch[1]);
return;
}
int nxt=x<tree[now].v?0:1;
if(!tree[now].ch[nxt])
{
int p=newpoint(x,now);
tree[now].ch[nxt]=p;
splay(p,tree[0].ch[1]);
return;
}
now=tree[now].ch[nxt];
}
}
}
delete
刪除的功能是:刪除權值爲v的節點
咱們不難想到:咱們能夠先找到他的位置,再把這個節點刪掉
找位置用find函數,不要和rotate的findd搞混
inline int find(register int v)
{
int now=tree[0].ch[1];
while(19260817)
{
if(tree[now].v==v)
{
splay(now,tree[0].ch[1]);
return now;
}
int nxt=v<tree[now].v?0:1;
if(!tree[now].ch[nxt])
return 0;
now=tree[now].ch[nxt];
}
}
下面咱們須要刪除函數
怎麼樣才能保證刪除節點後整棵樹還知足二叉查找樹的性質
此時會出現幾種狀況
1.權值爲v的節點已經出現過
這時候直接把他的rec和sum減去1就好
2.本節點沒有左右兒子
這樣的話就成了一棵空樹
3.本節點沒有左兒子
直接把他的右兒子設置成根
4.既有左兒子,又有右兒子
在它的左兒子中找到最大的,旋轉到根,把它的右兒子當作根(也就是它最大的左兒子)的右兒子
最後把這個節點刪掉就好
delete的代碼
inline void delet(register int x)
{
int pos=find(x);
if(!pos)
return;
if(tree[pos].rec>1)
{
--tree[pos].rec;
--tree[pos].sum;
}
else
{
if(!tree[pos].ch[0]&&!tree[pos].ch[1])
tree[0].ch[1]=0;
else if(!tree[pos].ch[0])
{
tree[0].ch[1]=tree[pos].ch[1];
tree[tree[0].ch[1]].fa=0;
}
else
{
int left=tree[pos].ch[0];
while(tree[left].ch[1])
left=tree[left].ch[1];
splay(left,tree[pos].ch[0]);
connect(tree[pos].ch[1],left,1);
connect(left,0,1);
update(left);
}
}
}
rank
1.查詢x數的排名
十分簡短
inline int rank(register int v)
{
int pos=find(v);
return tree[tree[pos].ch[0]].sum+1;
}
2.查詢排名爲x的數
這個操做就是上面那個操做的逆向操做
inline int arank(register int x)
{
int now=tree[0].ch[1];
while(19260817)
{
int used=tree[now].sum-tree[tree[now].ch[1]].sum;
if(x>tree[tree[now].ch[0]].sum&&x<=used)
{
splay(now,tree[0].ch[1]);
return tree[now].v;
}
if(x<used)
now=tree[now].ch[0];
else
x-=used,now=tree[now].ch[1];
}
}
求前驅和後繼
前驅
這個更容易,咱們能夠維護一個ans變量,而後對整棵樹進行遍歷,同時更新ans
inline int lower(register int v)
{
int now=tree[0].ch[1];
int ans=-inf;
while(now)
{
if(tree[now].v<v&&tree[now].v>ans)
ans=tree[now].v;
if(v>tree[now].v)
now=tree[now].ch[1];
else
now=tree[now].ch[0];
}
return ans;
}
後繼
和前驅差很少
inline int upper(register int v)
{
int now=tree[0].ch[1];
int ans=inf;
while(now)
{
if(tree[now].v>v&&tree[now].v<ans)
ans=tree[now].v;
if(v<tree[now].v)
now=tree[now].ch[0];
else
now=tree[now].ch[1];
}
return ans;
}
4.Spaly總體代碼
#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define N 100005
#define inf 1000000005
using namespace std;
inline int read()
{
register int x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register int x)
{
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[36];int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
struct node
{
int v;
int fa;
int ch[2];
int rec;
int sum;
}tree[N];
int tot;
inline void update(register int x)
{
tree[x].sum=tree[tree[x].ch[0]].sum+tree[tree[x].ch[1]].sum+tree[x].rec;
}
inline bool findd(register int x)
{
return tree[tree[x].fa].ch[0]==x?0:1;
}
inline void connect(register int x,register int fa,register int son) //把x轉爲fa的son(son是0/1,表示左孩子或右孩子)
{
tree[x].fa=fa;
tree[fa].ch[son]=x;
}
inline void rotate(register int x)
{
int Y=tree[x].fa;
int R=tree[Y].fa;
int Yson=findd(x);
int Rson=findd(Y);
int B=tree[x].ch[Yson^1];
connect(B,Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y),update(x);
}
inline void splay(register int x,register int to)
{
to=tree[to].fa;
while(tree[x].fa!=to)
{
int y=tree[x].fa;
if(tree[y].fa==to)
rotate(x);
else if(findd(x)==findd(y))
rotate(y),rotate(x);
else
rotate(x),rotate(x);
}
}
inline int newpoint(register int v,register int fa)
{
tree[++tot].fa=fa;
tree[tot].v=v;
tree[tot].sum=tree[tot].rec=1;
return tot;
}
inline void Insert(register int x)
{
int now=tree[0].ch[1];
if(tree[0].ch[1]==0)
{
newpoint(x,0);
tree[0].ch[1]=tot;
}
else
{
while(19260817)
{
++tree[now].sum;
if(tree[now].v==x)
{
++tree[now].rec;
splay(now,tree[0].ch[1]);
return;
}
int nxt=x<tree[now].v?0:1;
if(!tree[now].ch[nxt])
{
int p=newpoint(x,now);
tree[now].ch[nxt]=p;
splay(p,tree[0].ch[1]);
return;
}
now=tree[now].ch[nxt];
}
}
}
inline int find(register int v)
{
int now=tree[0].ch[1];
while(19260817)
{
if(tree[now].v==v)
{
splay(now,tree[0].ch[1]);
return now;
}
int nxt=v<tree[now].v?0:1;
if(!tree[now].ch[nxt])
return 0;
now=tree[now].ch[nxt];
}
}
inline void delet(register int x)
{
int pos=find(x);
if(!pos)
return;
if(tree[pos].rec>1)
{
--tree[pos].rec;
--tree[pos].sum;
}
else
{
if(!tree[pos].ch[0]&&!tree[pos].ch[1])
tree[0].ch[1]=0;
else if(!tree[pos].ch[0])
{
tree[0].ch[1]=tree[pos].ch[1];
tree[tree[0].ch[1]].fa=0;
}
else
{
int left=tree[pos].ch[0];
while(tree[left].ch[1])
left=tree[left].ch[1];
splay(left,tree[pos].ch[0]);
connect(tree[pos].ch[1],left,1);
connect(left,0,1);
update(left);
}
}
}
inline int rank(register int v)
{
int pos=find(v);
return tree[tree[pos].ch[0]].sum+1;
}
inline int arank(register int x)
{
int now=tree[0].ch[1];
while(19260817)
{
int used=tree[now].sum-tree[tree[now].ch[1]].sum;
if(x>tree[tree[now].ch[0]].sum&&x<=used)
{
splay(now,tree[0].ch[1]);
return tree[now].v;
}
if(x<used)
now=tree[now].ch[0];
else
x-=used,now=tree[now].ch[1];
}
}
inline int lower(register int v)
{
int now=tree[0].ch[1];
int ans=-inf;
while(now)
{
if(tree[now].v<v&&tree[now].v>ans)
ans=tree[now].v;
if(v>tree[now].v)
now=tree[now].ch[1];
else
now=tree[now].ch[0];
}
return ans;
}
inline int upper(register int v)
{
int now=tree[0].ch[1];
int ans=inf;
while(now)
{
if(tree[now].v>v&&tree[now].v<ans)
ans=tree[now].v;
if(v<tree[now].v)
now=tree[now].ch[0];
else
now=tree[now].ch[1];
}
return ans;
}
int main()
{
int m=read();
while(m--)
{
int opt=read(),x=read();
if(opt==1)
Insert(x);
else if(opt==2)
delet(x);
else if(opt==3)
{
write(rank(x));
printf("\n");
}
else if(opt==4)
{
write(arank(x));
printf("\n");
}
else if(opt==5)
{
write(lower(x));
printf("\n");
}
else
{
write(upper(x));
printf("\n");
}
}
return 0;
}
5.相關題目
以上是splay的基本應用qaq
還有一種操做沒講,就是如何進行區間操做
實現起來很簡單
假設咱們要在[l,r]之間上搞事情,咱們首先把l的前驅旋轉到根節點,再把r的後繼轉到根節點的右兒子
那麼此時根節點右兒子的左兒子表明的就是區間[l,r]
這應該很好理解qaq
而後就能夠像線段樹的lazy標記同樣,給區間l,rl,r打上標記,延遲更新,好比區間反轉的時候更新的時候直接交換左右兒子
這裏有一個奇技淫巧:若是一個區間被打了兩次,那麼就至關於不打
因此咱們用一個bool變量來儲存該節點是否須要被旋轉
pushdown函數能夠這麼寫(rev就是翻轉標記)
inline void pushdown(register int x)
{
if(tree[x].rev)
{
swap(tree[x].ch[0],tree[x].ch[1]);
tree[tree[x].ch[0]].rev^=1;
tree[tree[x].ch[1]].rev^=1;
tree[x].rev=0;
}
}
這道題完整代碼
#include <bits/stdc++.h>
#define N 100005
#define inf 0x7fffff
using namespace std;
inline int read()
{
register int x=0,f=1;register char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return x*f;
}
inline void write(register int x)
{
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[36];int cnt=0;
while(x)sta[cnt++]=x%10,x/=10;
while(cnt)putchar(sta[--cnt]+48);
}
int n,m;
struct node{
int fa,ch[2],tot;
bool rev;
}tree[N];
int root,PosL,PosR;
inline bool findd(register int x)
{
return x==tree[tree[x].fa].ch[1];
}
inline void connect(register int x,register int fa,register int son)
{
tree[x].fa=fa;
tree[fa].ch[son]=x;
}
inline void update(register int x)
{
tree[x].tot=tree[tree[x].ch[0]].tot+tree[tree[x].ch[1]].tot+1;
}
inline void rotate(register int x)
{
int Y=tree[x].fa;
if(Y==root)
root=x;
int R=tree[Y].fa;
int Yson=findd(x);
int Rson=findd(Y);
int B=tree[x].ch[Yson^1];
connect(B,Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y);
update(x);
}
inline void splay(register int x,register int to)
{
while(tree[x].fa!=to)
{
int y=tree[x].fa;
if(tree[y].fa==to)
rotate(x);
else if(findd(x)==findd(y))
rotate(y),rotate(x);
else
rotate(x),rotate(x);
}
update(x);
}
inline int buildsplay(register int l,register int r)
{
if(l>r)
return 0;
int mid=l+r>>1;
connect(buildsplay(l,mid-1),mid,0);
connect(buildsplay(mid+1,r),mid,1);
tree[mid].rev=0;
update(mid);
return mid;
}
inline void pushdown(register int x)
{
if(tree[x].rev)
{
swap(tree[x].ch[0],tree[x].ch[1]);
tree[tree[x].ch[0]].rev^=1;
tree[tree[x].ch[1]].rev^=1;
tree[x].rev=0;
}
}
inline int find(register int x)
{
int now=root;
--x;
pushdown(now);
while(x!=tree[tree[now].ch[0]].tot)
{
if(tree[tree[now].ch[0]].tot<x)
x-=tree[tree[now].ch[0]].tot+1,now=tree[now].ch[1];
else
now=tree[now].ch[0];
pushdown(now);
}
return now;
}
inline void print(register int now)
{
if(!now)
return;
pushdown(now);
print(tree[now].ch[0]);
if(now!=1&&now!=n+2)
write(now-1),putchar(' ');
print(tree[now].ch[1]);
}
int main()
{
n=read(),m=read();
root=buildsplay(1,n+2);
while(m--)
{
int l=read(),r=read();
PosL=find(l);
splay(PosL,0);
PosR=find(r+2);
splay(PosR,root);
tree[tree[PosR].ch[0]].rev^=1;
}
print(root);
return 0;
}