伸展樹(splay)入門學習(一) (洛谷 P3369 【模板】普通平衡樹)

題目描述

您需要寫一種數據結構(可參考題目標題),來維護一些數,其中需要提供以下操作:

  1. 插入 xx 數
  2. 刪除 xx 數(若有多個相同的數,因只刪除一個)
  3. 查詢 xx 數的排名(排名定義爲比當前數小的數的個數 +1+1 。若有多個相同的數,因輸出最小的排名)
  4. 查詢排名爲 xx 的數
  5. 求 xx 的前驅(前驅定義爲小於 xx ,且最大的數)
  6. 求 xx 的後繼(後繼定義爲大於 xx ,且最小的數)

輸入輸出格式

輸入格式:

 

第一行爲 nn ,表示操作的個數,下面 nn 行每行有兩個數 optopt 和 xx , optopt 表示操作的序號( 1 \leq opt \leq 61≤opt≤6 )

 

輸出格式:

 

對於操作 3,4,5,63,4,5,6 每行輸出一個數,表示對應答案

 

輸入輸出樣例

輸入樣例#1: 

10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

輸出樣例#1: 

106465
84185
492737

說明

時空限制:1000ms,128M

1.n的數據範圍: n \leq 100000n≤100000

2.每個數的數據範圍: [-{10}^7, {10}^7][−107,107]

 

學習了下伸展樹,伸展樹的功能真的很強大,直接粘板子就行,板子裏的註釋寫的挺詳細了

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
struct tree
{
    int father,lch,rch,val,cnt,size; //父節點、左兒子、右兒子、值、該值出現次數、子樹大小
}stree[1000005];
int root,tot; // 根節點,樹的大小
inline void clearr(int x)
{
    stree[x].lch = stree[x].rch = stree[x].father = stree[x].size = stree[x].cnt = stree[x].val = 0;
}
inline bool get(int x) // 判斷是父親的左兒子還是右兒子
{
    return stree[ stree[x].father ].rch == x;
}
inline void update(int x) // 更新子樹大小
{
    if (x)
    {
        stree[x].size = stree[x].cnt;   // 該值出現次數  + 左右子樹大小
        if (stree[x].lch)
            stree[x].size += stree[ stree[x].lch ].size;
        if (stree[x].rch)
            stree[x].size += stree[ stree[x].rch ].size;
    }
}
inline void rotate(int x)//旋轉
{
    int fa,oldf,whichx;
    fa = stree[x].father;  // x的父節點
    oldf = stree[fa].father;  // x父節點的父節點
    whichx = get(x); // 判斷x是父節點左兒子還是右兒子
    if (whichx) //x是父節點的右兒子
    {
        stree[fa].rch = stree[x].lch;
        stree[ stree[fa].rch ].father = fa; // 將x的左兒子變爲x父節點的右兒子
        stree[x].lch = fa;
        stree[fa].father = x;
        stree[x].father = oldf; // 將x父節點變爲x左兒子
    }
    else    // 同上
    {
        stree[fa].lch = stree[x].rch;
        stree[ stree[fa].lch ].father = fa;
        stree[x].rch = fa;
        stree[fa].father = x;
        stree[x].father = oldf;
    }
    if (oldf) // 如果有x的父節點有父節點,則更新祖父節點的(左/右)兒子爲x
    {
        if (stree[oldf].rch == fa)
            stree[oldf].rch = x;
        else
            stree[oldf].lch = x;
    }
    update(fa);
    update(x);  // 更新x與x父節點的大小
}
inline void splay(int x, int t)//將x伸展到t的子節點
{
    int fa = stree[x].father; //將x伸展至父節點
    while (fa != t)
    {
        if (stree[fa].father) // 如果父節點有父節點
        {
            if (get(x) == get(fa)) // 一字型(需旋轉父節點再旋轉x)
                rotate(fa);
            else
                rotate(x); // 之字形(需旋轉x兩次)
        }
        rotate(x); // 旋轉x
        fa = stree[x].father; //更新父節點
    }
    if (!t)
        root = x; // 更新根節點
}
inline void insert(int x) // 插入x
{
    if (!root) // 如果splay樹爲空
    {
        tot++;
        stree[tot].lch = stree[tot].rch = stree[tot].father = 0;
        root = tot;
        stree[tot].size = stree[tot].cnt = 1;
        stree[tot].val = x;
        return;
    }
    int u = root;
    int fa = 0;
    while (1)
    {
        if (x == stree[u].val) // 如果splay樹裏已經有這個值
        {
            stree[u].cnt++;
            update(u);
            update(fa);
            splay(u, 0);
            return;
        }
        fa = u;
        if (stree[u].val < x)
            u = stree[u].rch;
        else
            u = stree[u].lch;   // 找該值應該是在當前節點的左/右子樹內
        if (!u) // 如果找到x應作爲fa節點的左/右兒子
        {
            tot++; // 樹大小+1
            stree[tot].lch = stree[tot].rch = 0;
            stree[tot].father = fa;
            stree[tot].size = stree[tot].cnt = 1;
            if (stree[fa].val < x)
                stree[fa].rch = tot;
            else
                stree[fa].lch = tot;
            stree[tot].val = x;  // 創建該節點
            update(fa); // 更新父節點大小
            splay(tot, 0); // 將當前節點伸展到根
            return;
        }
    }
}
int find(int x) //查找x爲第幾小的值
{
    int ans = 0;
    int u = root;
    while (1)
    {
        if (x < stree[u].val) // 如果x在當前節點的左子樹內
            u = stree[u].lch;
        else // 如果x在當前節點的右子樹內
        {
            if (stree[u].lch) //當前節點的左子樹都比x小
                ans += stree[ stree[u].lch ].size;
            if (x == stree[u].val) // 如果x是當前節點,則找到排名
            {
                splay(u, 0); // 將x伸展到根節點
                return ans + 1;
            }
            ans += stree[u].cnt; //  當前節點比x小
            u = stree[u].rch;  //  更新當前節點
        }
    }
}
int findx(int x)//找第x小的點
{
    int u = root;
    while (1)
    {
        if (stree[u].lch && stree[ stree[u].lch ].size >= x) // 如果當前節點的左子樹大小已經大於等於x,即就在當前節點的左子樹內找
            u = stree[u].lch;
        else
        {
            int tmp = stree[u].cnt;
            if (stree[u].lch)
                tmp += stree[ stree[u].lch ].size;
            if (tmp >= x)   // 當前值即爲要尋找的值
                return stree[u].val;
            x -= tmp;   // 已經找到最小的tmp個樹
            u = stree[u].rch; // 去當前節點的有子樹內找
        }
    }
}
inline int pre()//查找x的前驅(需先插入x,查找完後刪除,返回值爲前驅的下標)
{
    int now = stree[root].lch; //插入x時,x已經作爲樹的根,在x的左子樹內找最大值
    while (stree[now].rch)
        now = stree[now].rch;
    return now;
}
inline int next()//查找x的後繼(需先插入x,查找完後刪除,返回值爲後繼的下標)
{
    int now = stree[root].rch; //插入x時,x已經作爲樹的根,在x的右子樹內找最小值
    while (stree[now].lch)
        now = stree[now].lch;
    return now;
}
void deletee(int x) // 刪除x
{
    int p = find(x);  // 此時x已經成爲根節點
    if (stree[root].cnt > 1) // 如果x出現次數超過一次
    {
        stree[root].cnt--;
        update(root);
        return;
    }
    if (!stree[root].lch && !stree[root].rch)  // 如果x沒有左右子樹,直接刪除即可
    {
        clearr(root);
        root = 0;
        return;
    }
    if (!stree[root].lch) //如果x沒有左子樹,則將x的右兒子作爲根,直接刪除x
    {
        int tmp = root;
        root = stree[root].rch;
        stree[root].father = 0;
        clearr(tmp);
        return;
    }
    if (!stree[root].rch)//如果x沒有右子樹,則將x的左兒子作爲根,直接刪除x
    {
        int tmp = root;
        root = stree[root].lch;
        stree[root].father = 0;
        clearr(tmp);
        return;
    }
    int left = pre(); // 找x的前驅
    int tmp = root;
    splay(left, 0); // 將x的前驅伸展到根節點
    stree[root].rch = stree[tmp].rch;
    stree[ stree[tmp].rch ].father = root; // x的右兒子作爲x前驅的右兒子
    clearr(tmp); // 刪除x節點
    update(root); // 更新樹大小
    return;
}
int main()
{
//    freopen("in.txt", "r", stdin);
    int n;
    scanf("%d", &n);
    while (n--)
    {
        int f,x;
        scanf("%d%d", &f, &x);
        if (f == 1)
            insert(x);
        if (f == 2)
            deletee(x);
        if (f == 3)
            printf("%d\n", find(x));
        if (f == 4)
            printf("%d\n", findx(x));
        if (f == 5)
        {
            insert(x);
            printf("%d\n", stree[pre()].val);
            deletee(x);
        }
        if (f == 6)
        {
            insert(x);
            printf("%d\n", stree[next()].val);
            deletee(x);
        }
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章