前言
在上一節中,我們講述了Splay的核心操作rotate與splay
本節我會教大家如何用這兩個函數實現各種強大的功能
為了方便講解,我們拿這道題做例題來慢慢分析
利用splay實現各種功能
首先,我們需要定義一些東西
各種指針
struct node
{
int v;//權值
int fa;//父親節點
int ch[2];//0代表左兒子,1代表右兒子
int rec;//這個權值的節點出現的次數
int sum;//子節點的數量
};
int tot;//tot表示不算重復的有多少節點
rotate
splay
這兩個函數就不講了,前面已經講的挺詳細了
插入
根據前面講的,我們在插入一個數之后,需要將其旋轉到根
首先
當這棵樹已經沒有節點的時候,我們直接新建一個節點就好
inline int newpoint(int v,int fa)//v:權值;fa:它的爸爸是誰
{
tree[++tot].fa=fa;
tree[tot].v=v;
tree[tot].sum=tree[tot].rec=1;
return tot;
}
當這可樹有節點的時候,我們根據二叉查找樹的性質,不斷向下走,直到找到一個可以插入的點,注意在走的時候需要更新一個每個節點的sum值
void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
if(!T[now].ch[nxt])
{
int p=newnode(x,now);
T[now].ch[nxt]=p;
splay(p,root);return ;
}
now=T[now].ch[nxt];
}
}
}
刪除
刪除的功能是:刪除權值為v的節點
我們不難想到:我們可以先找到他的位置,再把這個節點刪掉
int find(int v)
{
int now=root;
while(1)
{
if(tree[now].v==v) {splay(now,root);return now;}
int nxt=v<tree[now].v?0:1;
if(!tree[now].ch[nxt])return 0;
now=tree[now].ch[nxt];
}
}
這個函數可以找到權值為v的節點的位置,比較好理解,注意別忘記把找到的節點splay到根
另外我們還需要一個徹底刪除的函數
update:
這個函數其實是不需要的,因為后面的節點一定會覆蓋前面的節點
inline void dele(int x)
{
tree[x].sum=tree[x].v=tree[x].rec=tree[x].fa=tree[x].ch[0]=tree[x].ch[1]=0;
if(x==tot) tot--;
}
接下來的任務就是怎么樣才能保證刪除節點后整棵樹還滿足二叉查找樹的性質
注意:我們在查找完一個節點的時候已經將他旋轉到根了,所以他左邊一定都比他小,除此之外沒有比他小的節點了(否則還要考慮他父親比他小的情況)
那么此時會出現幾種情況
- 權值為v的節點已經出現過
這時候直接把他的rec和sum減去1就好 - 本節點沒有左右兒子
這樣的話就成了一棵空樹 - 本節點沒有左兒子
直接把他的右兒子設置成根 - 既有左兒子,又有右兒子
在它的左兒子中找到最大的,旋轉到根,把它的右兒子當做根(也就是它最大的左兒子)的右兒子
最后把這個節點刪掉就好
void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}
查詢x數的排名
update in 2018.4.10
以前的自己too naive
這個函數這么寫就好
int rak(int val)
{
int pos=find(val);
return T[ls(pos)].siz+1;
}
這個簡單,如果我們找到了權值為x的節點,那么答案就是他的左子樹的大小+1
否則的話根據二叉查找樹的性質不斷的向下走就可以,注意如果這次是向右走的話答案需要加上它左子樹的大小和這個節點的rec值
int rank(int v)// 查詢值為v的數的排名
{
int ans=0,now=root;
while(1)
{
if(tree[now].v==v) return ans+tree[tree[now].ch[0]].sum+1;
if(now==0) return 0;
if(v<tree[now].v) now=tree[now].ch[0];
else ans+=tree[tree[now].ch[0]].sum+tree[now].rec,now=tree[now].ch[1];
}
if(now) splay(now,root);
return 0;
}
查詢排名為x的數
這個操作就是上面那個操作的逆向操作
用used變量記錄該節點以及它的左子樹有多少節點
如果x>左子樹的數量且< used,那么當前節點的權值就是答案
否則根據二叉查找樹的性質繼續向下走
同樣注意在向右走的時候要更新x
int arank(int x)//查詢排名為x的數是什么
{
int now=root;
while(1)
{
int used=tree[now].sum-tree[tree[now].ch[1]].sum;
if(x>tree[tree[now].ch[0]].sum&&x<=used) break;
if(x<used) now=tree[now].ch[0];
else x=x-used,now=tree[now].ch[1];
}
splay(now,root);
return tree[now].v;
}
求x的前驅
這個更容易,我們可以維護一個ans變量,然后對整棵樹進行遍歷,同時更新ans
int lower(int v)// 小於v的最大值
{
int now=root;
int ans=-maxn;
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;
}
求x的后繼
這個和上一個一樣,就不細講了
int upper(int v)
{
int now=root;
int ans=maxn;
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;
}
完整代碼:
// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ls(x) T[x].ch[0]
#define rs(x) T[x].ch[1]
#define fa(x) T[x].fa
#define root T[0].ch[1]
using namespace std;
const int MAXN=1e5+10,mod=10007,INF=1e9+10;
inline char nc()
{
static char buf[MAXN],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin)),p1==p2?EOF:*p1++;
}
inline int read()
{
char c=nc();int x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
return x*f;
}
struct node
{
int fa,ch[2],val,rec,sum;
}T[MAXN];
int tot=0,pointnum=0;
void update(int x){T[x].sum=T[ls(x)].sum+T[rs(x)].sum+T[x].rec;}
int ident(int x){return T[fa(x)].ch[0]==x?0:1;}
void connect(int x,int fa,int how){T[fa].ch[how]=x;T[x].fa=fa;}
void rotate(int x)
{
int Y=fa(x),R=fa(Y);
int Yson=ident(x),Rson=ident(Y);
connect(T[x].ch[Yson^1],Y,Yson);
connect(Y,x,Yson^1);
connect(x,R,Rson);
update(Y);update(x);
}
void splay(int x,int to)
{
to=fa(to);
while(fa(x)!=to)
{
int y=fa(x);
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)
{
T[++tot].fa=f;
T[tot].rec=T[tot].sum=1;
T[tot].val=v;
return tot;
}
void Insert(int x)
{
int now=root;
if(root==0) {newnode(x,0);root=tot;}//
else
{
while(1)
{
T[now].sum++;
if(T[now].val==x) {T[now].rec++;splay(now,root);return ;}
int nxt=x<T[now].val?0:1;
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)
{
int now=root;
while(1)
{
if(!now) return 0;
if(T[now].val==x) {splay(now,root);return now;}
int nxt=x<T[now].val?0:1;
now=T[now].ch[nxt];
}
}
void delet(int x)
{
int pos=find(x);
if(!pos) return ;
if(T[pos].rec>1) {T[pos].rec--,T[pos].sum--;return ;}
else
{
if(!T[pos].ch[0]&&!T[pos].ch[1]) {root=0;return ;}
else if(!T[pos].ch[0]) {root=T[pos].ch[1];T[root].fa=0;return ;}
else
{
int left=T[pos].ch[0];
while(T[left].ch[1]) left=T[left].ch[1];
splay(left,T[pos].ch[0]);
connect(T[pos].ch[1],left,1);
connect(left,0,1);//
update(left);
}
}
}
int rak(int x)
{
int now=root,ans=0;
while(1)
{
if(T[now].val==x) return ans+T[T[now].ch[0]].sum+1;
int nxt=x<T[now].val?0:1;
if(nxt==1) ans=ans+T[T[now].ch[0]].sum+T[now].rec;
now=T[now].ch[nxt];
}
}
int kth(int x)//排名為x的數
{
int now=root;
while(1)
{
int used=T[now].sum-T[T[now].ch[1]].sum;
if(T[T[now].ch[0]].sum<x&&x<=used) {splay(now,root);return T[now].val;}
if(x<used) now=T[now].ch[0];
else now=T[now].ch[1],x-=used;
}
}
int lower(int 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?0:1;//這里需要特別注意
now=T[now].ch[nxt];
}
return ans;
}
int upper(int 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?0:1;
now=T[now].ch[nxt];
}
return ans;
}
int main()
{
#ifdef WIN32
freopen("a.in","r",stdin);
#else
#endif
int N=read();
while(N--)
{
int opt=read(),x=read();
if(opt==1) Insert(x);
else if(opt==2) delet(x);
else if(opt==3) printf("%d\n",rak(x));
else if(opt==4) printf("%d\n",kth(x));
else if(opt==5) printf("%d\n",lower(x));
else if(opt==6) printf("%d\n",upper(x));
}
return 0;
}
至此,splay最常用的幾種函數就解決了,
下面來看幾道裸題
例題
不知道為什么,我的splay跑的特別快,可能是臉太好了吧😂
洛谷P2234 [HNOI2002]營業額統計
http://www.cnblogs.com/zwfymqz/p/7896128.html