洛谷P3369 普通平衡樹(Splay)

Self-Discipline發表於2018-09-05

題目:點選開啟連結

題意:中文題,不解釋。

分析:這題是平衡樹操作的裸題,用treap或者splay都行,我這裡用的是splay。splay入門推薦https://blog.csdn.net/clove_unique/article/details/50630280,寫的非常詳細。不知道為啥find的時候一定要翻轉,不翻轉就會wa,歡迎大佬留言指教。

程式碼:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize(4)
#pragma comment(linker, "/STACK:102400000,102400000")
#include<unordered_map>
#include<unordered_set>
#include<algorithm>
#include<iostream>
#include<fstream>
#include<complex>
#include<cstdlib>
#include<cstring>
#include<cassert>
#include<iomanip>
#include<string>
#include<cstdio>
#include<bitset>
#include<vector>
#include<cctype>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<list>
#include<set>
#include<map>
using namespace std;
#define pt(a) cout<<a<<endl
#define debug test
#define mst(ss,b) memset((ss),(b),sizeof(ss))
#define rep(i,a,n) for (int i=a;i<=n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
#define ll long long
#define ull unsigned long long
#define pb push_back
#define mp make_pair
#define inf 0x3f3f3f3f
#define eps 1e-10
#define PI acos(-1.0)
typedef pair<int,int> PII;
const ll mod = 1e9+7;
const int N = 1e6+10;

ll gcd(ll p,ll q){return q==0?p:gcd(q,p%q);}
ll qp(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
int to[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

int ch[N][2],f[N],size[N],cnt[N],key[N];
int sz,root;

void clear(int x) {///清空節點
    ch[x][0]=ch[x][1]=f[x]=size[x]=cnt[x]=key[x]=0;
}

bool get(int x) {///判斷左右兒子
    return ch[f[x]][1]==x;
}

void update(int x) {///更新包括x的子樹大小
    if(x) {
        size[x]=cnt[x];
        if(ch[x][0]) size[x]+=size[ch[x][0]];
        if(ch[x][1]) size[x]+=size[ch[x][1]];
    }
}

void rotate(int x) {///旋轉
    int old=f[x],oldf=f[old],whichx=get(x);///找到父親和祖先 判斷左右兒子
    ch[old][whichx]=ch[x][whichx^1]; f[ch[old][whichx]]=old;///三步走之一
    ch[x][whichx^1]=old;f[old]=x;///三步走之二
    f[x]=oldf;///三步走之三
    if(oldf) ch[oldf][ch[oldf][1]==old]=x;
    update(old); update(x);
}

void splay(int x) {///伸展 即多次旋轉 直到根
    for(int fa;fa=f[x];rotate(x))
        if(f[fa]&&(get(x)==get(fa))) rotate(fa);///如果兒子 父親 祖父一條線 先旋轉父親,否則會形成單旋使平衡樹失衡
    root=x;
}

void insert(int x) {///插入x
    if(root==0) { sz++; ch[sz][0]=ch[sz][1]=f[sz]=0; root=sz; size[sz]=cnt[sz]=1; key[sz]=x; return; }///空樹特判
    int now=root,fa=0;
    while(1) {
        if(x==key[now]) {///結點的關鍵字等於當前要插入的點
            cnt[now]++;///now結點的關鍵字出現的次數(權值)+1
            update(now);///更新當前節點的子樹節點個數
            update(fa);///更新父親節點的子樹節點個數
            splay(now);///伸展 把now改為root
            break;
        }
        fa=now;///向下查詢左右子樹
        now=ch[now][key[now]<x];
        if(now==0) {///如果已經到了最底下了,那麼就可以直接插入
            sz++;///更新相關資訊
            ch[sz][0]=ch[sz][1]=0;
            f[sz]=fa;
            size[sz]=cnt[sz]=1;
            ch[fa][key[fa]<x]=sz;
            key[sz]=x;
            update(fa);
            splay(sz);
            break;
        }
    }
}

int find(int x) {///查詢x的排名
    int now=root,ans=0;
    while(1) {
        if(x<key[now]) now=ch[now][0];///如果x比當前結點小,即應該向左子樹尋找,ans不用改變
        else {
            ans += (ch[now][0]?size[ch[now][0]]:0);///如果x比當前結點大,即應該向右子樹尋找,ans需要加上左子樹的大小以及根的大小
            if(x==key[now]) {///不要忘記了再splay一下
                splay(now); return ans+1;
            }
            ans += cnt[now];
            now = ch[now][1];
        }
    }
}

int findx(int x) {///找到排名為x的點
    int now=root;
    while(1) {
        if(ch[now][0]&&x<=size[ch[now][0]]) now=ch[now][0];///如果當前點有左子樹,並且x比左子樹的大小小的話,即向左子樹尋找
        else {
            ///否則,向右子樹尋找:先判斷是否有右子樹,然後記錄右子樹的大小以及當前點的大小(都為權值),
            ///用於判斷是否需要繼續向右子樹尋找。
            int temp=(ch[now][0]?size[ch[now][0]]:0)+cnt[now];
            if(x<=temp) return key[now];
            x-=temp; now = ch[now][1];
        }
    }
}

int pre() {///求x的前驅 前驅定義為小於x,且最大的數
    int now=ch[root][0];///求x的前驅其實就是求x的左子樹的最右邊的一個結點
    while(ch[now][1]) now=ch[now][1];
    return now;
}

int next() {///求x的後繼 後繼定義為大於x,且最小的數
    int now=ch[root][1];///後繼是求x的右子樹的左邊一個結點
    while(ch[now][0]) now=ch[now][0];
    return now;
}

void del(int x) {///刪除操作
    int whatever=find(x);///隨便find一下x。目的是:將x旋轉到根。
    if(cnt[root]>1) { cnt[root]--; update(root); return; }///如果cnt[root]>1,即不只有一個x的話,直接-1返回。
    if(!ch[root][0]&&!ch[root][1]) { clear(root); root=0; return; }///如果root並沒有孩子,就說名樹上只有一個x而已,直接clear返回。
    if(!ch[root][0]) {///如果root只有右兒子,那麼直接clear root,然後把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
        int oldroot=root; root=ch[root][1]; f[root]=0; clear(oldroot); return;
    }else if(!ch[root][1]) {///如果root只有左兒子,那麼直接clear root,然後把唯一的兒子當作根就可以了(f賦0,root賦為唯一的兒子)
        int oldroot=root; root=ch[root][0]; f[root]=0; clear(oldroot); return;
    }
    int leftbig=pre(),oldroot=root;///我們找到新根,也就是x的前驅(x左子樹最大的一個點),將它旋轉到根。
    splay(leftbig);
    ch[root][1]=ch[oldroot][1];///然後將原來x的右子樹接到新根的右子樹上(注意這個操作需要改變父子關係)。這實際上就把x刪除了。
    f[ch[oldroot][1]]=root;
    clear(oldroot);///清空原根
    update(root);///不要忘了update新根。
}

int main() {
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    int n,opt,x;
    cin>>n;
    rep(i,1,n) {
        cin>>opt>>x;
        if(opt==1) insert(x);
        else if(opt==2) del(x);
        else if(opt==3) cout<<find(x)<<endl;
        else if(opt==4) cout<<findx(x)<<endl;
        else if(opt==5) insert(x),cout<<key[pre()]<<endl,del(x);///問題可以轉化為將x插入,求出樹上的前驅(後繼),再將x刪除的問題。
        else if(opt==6) insert(x),cout<<key[next()]<<endl,del(x);
    }
    return 0;
}

 

相關文章