1. 程式人生 > 其它 >『學習筆記』Splay

『學習筆記』Splay

不打算詳細寫了,強推一波 yyb 神仙的部落格 Splay入門解析【保證讓你看不懂(滑稽)】

(這篇部落格的程式碼完全是按照 yyb 的部落格寫的,並有一些補充,包括 pushup 及查詢第 k 大的整數等等)

這裡列幾個注意事項吧:

  • \(Splay\) 過程中,如果 \(x, y\) 為同一種兒子,那麼先旋轉 \(y\),再轉 \(x\),否則旋轉兩次 \(x\)
  • \(pushup\) 不要忘記寫。

emm……別的似乎就沒了。

(以後有時間的話可能會稍微寫的詳細一點,先咕了)

\(Code:\)

(原題是洛谷 P3369,程式碼裡有為詳細的註釋)

#include <bits/stdc++.h>
#define ls(x) t[x].ch[0]
#define rs(x) t[x].ch[1]

using namespace std;

namespace IO{
    inline int read(){
        int x = 0, f = 1;
        char ch = getchar();
        while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x * f;
    }

    template <typename T> inline void write(T x){
        if(x < 0) putchar('-'), x = -x;
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const int N = 1e6 + 10;
int n;

struct splay{
    int val, siz, fa, ch[2], cnt;
}t[N];
int root, tot;

inline void pushup(int x){
    t[x].siz = t[ls(x)].siz + t[rs(x)].siz + t[x].cnt;
}

//旋轉
inline void rotate(int x){
    int y = t[x].fa, z = t[y].fa;
    int k = (rs(y) == x);// 0 是左兒子,1 是右兒子
    t[z].ch[(rs(z) == y)] = x;//z 原來的 y 位置變成 x
    t[x].fa = z;
    t[y].ch[k] = t[x].ch[k ^ 1];//x 的另一個兒子變成 x 在 y 原本位置上的兒子
    t[t[x].ch[k ^ 1]].fa = y;//更新父親
    t[x].ch[k ^ 1] = y, t[y].fa = x;//更新 x 和 y 的關係
    pushup(y), pushup(x);
}

//核心 splay
inline void splay(int x, int goal){
    while(t[x].fa != goal){
        int y = t[x].fa, z = t[y].fa;
        if(z != goal) rotate((ls(z) == y) ^ (ls(y) == x) ? x : y);//splay 雙旋分類討論
        rotate(x);
    }
    if(!goal) root = x;
}

//查詢 x 的位置,並 splay 到根
inline void find(int x){
    int u = root;
    if(!u) return;
    while(t[u].ch[x > t[u].val] && x != t[u].val) u = t[u].ch[x > t[u].val];//小於 -> 往左跳,大於 -> 往右跳
    splay(u, 0);
}

//插入一個新節點
inline void insert(int x){
    int u = root, fa = 0;
    while(u && t[u].val != x) fa = u, u = t[u].ch[x > t[u].val];//找到等於 x 的點
    if(u) t[u].cnt++;//如果有,計數 +1
    else{
        u = ++tot;//沒有,新建節點
        if(fa) t[fa].ch[x > t[fa].val] = u;
        t[u].ch[0] = t[u].ch[1] = 0;
        t[u].siz = t[u].cnt = 1, t[u].val = x, t[u].fa = fa;
    }
    splay(u, 0);
}

//查詢 前驅/後繼
inline int check(int x, int type){//type = 0 前驅,1 後繼
    find(x);
    int u = root;//根節點,此時 x 的父節點(存在的話)就是根節點
    if((t[u].val > x && type) || (t[u].val < x && !type)) return u;
    u = t[u].ch[type];
    while(t[u].ch[type ^ 1]) u = t[u].ch[type ^ 1];//反著跳
    return u;
}

//刪除
inline void remove(int x){
    int lst = check(x, 0), nxt = check(x, 1);//查 前驅/後繼
    splay(lst, 0), splay(nxt, lst);//前驅 splay 到根,後繼 splay 到前驅,x 是後繼的左子節點,且是葉節點
    int del = ls(nxt);
    if(t[del].cnt > 1) t[del].cnt--, splay(del, 0);//如果個數 > 1, 直接--
    else t[nxt].ch[0] = 0;//只有一個,刪掉 nxt 的左兒子
    pushup(nxt), pushup(lst);
}

//查詢第 k 大
inline int get_val(int x, int k){
    if(k <= t[ls(x)].siz) return get_val(ls(x), k);
    else if(k > t[ls(x)].siz + t[x].cnt) return get_val(rs(x), k - t[ls(x)].siz - t[x].cnt);
    else return t[x].val;
}

int main(){
    n = read();
    insert(-2147483647), insert(2147483647);
    for(int i = 1; i <= n; ++i){
        int op = read(), x = read();
        if(op == 1) insert(x);
        else if(op == 2) remove(x);
        else if(op == 3) find(x), write(t[ls(root)].siz), puts("");
        else if(op == 4) write(get_val(root, x + 1)), puts("");
        else if(op == 5) write(t[check(x, 0)].val), puts("");
        else write(t[check(x, 1)].val), puts("");
    }
    return 0;
}
\[\_EOF\_ \]

本文來自部落格園,作者:xixike,轉載請註明原文連結:https://www.cnblogs.com/xixike/p/15729172.html