splay詳解(二)

前言

上一節中,咱們講述了Splay的核心操做rotate與splay
本節我會教你們如何用這兩個函數實現各類強大的功能
爲了方便講解,咱們拿這道題作例題來慢慢分析html

利用splay實現各類功能

首先,咱們須要定義一些東西node

各類指針

struct node
    {
        int v;//權值
        int fa;//父親節點
        int ch[2];//0表明左兒子,1表明右兒子
        int rec;//這個權值的節點出現的次數
        int sum;//子節點的數量
    };
    int tot;//tot表示不算重複的有多少節點

rotate

splay

這兩個函數就不講了,前面已經講的挺詳細了c++

插入

根據前面講的,咱們在插入一個數以後,須要將其旋轉到根
首先
當這棵樹已經沒有節點的時候,咱們直接新建一個節點就好ide

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的節點
咱們不難想到:咱們能夠先找到他的位置,再把這個節點刪掉spa

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到根指針

另外咱們還須要一個完全刪除的函數code

update:
這個函數實際上是不須要的,由於後面的節點必定會覆蓋前面的節點htm

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--;
}

接下來的任務就是怎麼樣才能保證刪除節點後整棵樹還知足二叉查找樹的性質
注意:咱們在查找完一個節點的時候已經將他旋轉到根了,因此他左邊必定都比他小,除此以外沒有比他小的節點了(不然還要考慮他父親比他小的狀況)blog

那麼此時會出現幾種狀況

  • 權值爲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

洛谷P2286 [HNOI2004]寵物收養場

http://www.cnblogs.com/zwfymqz/p/7895794.html

相關文章
相關標籤/搜索