洛谷P3369 普通平衡樹之板子

Nightmares_oi發表於2024-08-03

洛谷P3369題解

傳送錨點

摸魚環節

【模板】普通平衡樹

題目描述

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

  1. 插入一個數 \(x\)
  2. 刪除一個數 \(x\)(若有多個相同的數,應只刪除一個)。
  3. 定義排名為比當前數小的數的個數 \(+1\)。查詢 \(x\) 的排名。
  4. 查詢資料結構中排名為 \(x\) 的數。
  5. \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)。
  6. \(x\) 的後繼(後繼定義為大於 \(x\),且最小的數)。

對於操作 3,5,6,不保證當前資料結構中存在數 \(x\)

輸入格式

第一行為 \(n\),表示操作的個數,下面 \(n\) 行每行有兩個數 \(\text{opt}\)\(x\)\(\text{opt}\) 表示操作的序號($ 1 \leq \text{opt} \leq 6 $)

輸出格式

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

樣例 #1

樣例輸入 #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

簡單分析下題目,很明顯可以選擇用平衡樹來AC他,一般來說旋轉平衡樹是基礎,但我不會啊FHQtreep明顯更有價效比,作為20世紀的好青年,當然是什麼簡單寫什麼選擇順應時代的寫法,所以這題成為了FHQtreep訓練雞。



正片開始

1. 合理建樹

直接選擇結構體來處理樹。
咱需要:

  • l,r表示左子樹和右子樹;
  • val儲存權值;
  • rnd儲存隨機權值;
  • size儲存當前樹的大小;
  • 寫一個newnode函式存入新建點;

code:

struct 
{
    int l,r;
    int val;
    int rnd;
    int size;
}tr[N];
int root=0,n=0;
inline int _rand(){return rnd();}
int newnode(int v)
{
    tr[++n].val=v;
    tr[n].rnd=_rand();
    tr[n].size=1;
    return n;
}

2.處理更新必要部分

將左右子樹更新後加上自己,很簡單,但別忘了。

code:

void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}

3.核心操作之分裂合併

  1. 對於分裂,遞迴處理即可。勿忘更新。
void split(int p,int v,int &x,int &y)
{
    if(!p) {x=y=0;return;}
    if(tr[p].val<=v)
    {
        x=p;
        split(tr[p].r,v,tr[p].r,y);
    }
    else
    {
        y=p;
        split(tr[p].l,v,x,tr[p].l);
    }
    pushup(p);
}
  1. 對於合併操作,如果子樹為空,直接相加即可。選擇小根隊,判斷下左子樹與右子樹的隨機權值,將較小的作為根即可。記得更新。

code:

int merge(int x,int y)
{
    if(!x||!y) {return x+y;}
    if(tr[x].rnd<tr[y].rnd)
    {
        tr[x].r=merge(tr[x].r,y);
        pushup(x);
        return x;
    }
    else
    {
        tr[y].l=merge(x,tr[y].l);
        pushup(y);
        return y;
    }
}

4. 其餘操作處理

太水了,分分合合就行了

  • 增加操作,將樹以v分成兩個子樹,建立一個新的插入點,可視作第三棵子樹,然後將三棵子樹合併即可。

code:

void insert(int v)
{
    int x,y;
    split(root,v,x,y);
    int z=newnode(v);
    root=merge(merge(x,z),y);
} 
  • 刪除操作只需將樹分成三棵子樹,並將所需要刪除的點的樹上左子樹和右子樹直接合並即可。

code:

void del(int v)
{
    int x,y,z;
    split(root,v,x,z);
    split(x,v-1,x,y);
    y=merge(tr[y].l,tr[y].r);
    root=merge(merge(x,y),z);
}
  • 接下來的操作都可以參考上述方法,很容易得出結果。

code:

int rank(int v)
{
    int x,y;
    split(root,v-1,x,y);
    int ans=tr[x].size+1;
    root=merge(x,y);
    return ans;
}
int topk(int p,int k)
{
    int lsz=tr[tr[p].l].size;
    if(k==lsz+1) return tr[p].val;
    if(k<=lsz) return topk(tr[p].l,k);
    return topk(tr[p].r,k-lsz-1);
}
int get_pre(int v)
{
    int x,y;
    split(root,v-1,x,y);
    int ans=topk(x,tr[x].size);
    root=merge(x,y);
    return ans;
}
int get_suc(int v)
{
    int x,y;
    split(root,v,x,y);
    int ans=topk(y,1);
    root=merge(x,y);
    return ans;
}

函式部分到此結束。


完整程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+5;
unsigned long long seed=1;
std::mt19937 rnd(std::random_device{}());
struct fhq
{
    struct 
    {
       int l,r;
       int val;
       int rnd;
       int size;
    }tr[N];
    int root=0,n=0;
    inline int _rand(){return rnd();}
    int newnode(int v)
    {
        tr[++n].val=v;
        tr[n].rnd=_rand();
        tr[n].size=1;
        return n;
    }
    void pushup(int p){tr[p].size=tr[tr[p].l].size+tr[tr[p].r].size+1;}
    void split(int p,int v,int &x,int &y)
    {
        if(!p) {x=y=0;return;}
        if(tr[p].val<=v)
        {
            x=p;
            split(tr[p].r,v,tr[p].r,y);
        }
        else
        {
            y=p;
            split(tr[p].l,v,x,tr[p].l);
        }
        pushup(p);
    }
    int merge(int x,int y)
    {
        if(!x||!y) {return x+y;}
        if(tr[x].rnd<tr[y].rnd)
        {
            tr[x].r=merge(tr[x].r,y);
            pushup(x);
            return x;
        }
        else
        {
            tr[y].l=merge(x,tr[y].l);
            pushup(y);
            return y;
        }
    }
    explicit fhq() { memset(tr, 0, sizeof tr); }
    int size() { return tr[root].size; }
    void insert(int v)
    {
        int x,y;
        split(root,v,x,y);
        int z=newnode(v);
        root=merge(merge(x,z),y);
    } 
    void del(int v)
    {
        int x,y,z;
        split(root,v,x,z);
        split(x,v-1,x,y);
        y=merge(tr[y].l,tr[y].r);
        root=merge(merge(x,y),z);
    }
    int rank(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=tr[x].size+1;
        root=merge(x,y);
        return ans;
    }
    int topk(int p,int k)
    {
        int lsz=tr[tr[p].l].size;
        if(k==lsz+1) return tr[p].val;
        if(k<=lsz) return topk(tr[p].l,k);
        return topk(tr[p].r,k-lsz-1);
    }
    int get_pre(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=topk(x,tr[x].size);
        root=merge(x,y);
        return ans;
    }
    int get_suc(int v)
    {
        int x,y;
        split(root,v,x,y);
        int ans=topk(y,1);
        root=merge(x,y);
        return ans;
    }
};
int main()
{
    int n,m;
    cin>>m;
    fhq t;
    int ans=0;
    while(m--)
    {
        int op,x;cin>>op>>x;
        if(op==1) t.insert(x);
        if(op==2) t.del(x);
        if(op==3) cout<<t.rank(x)<<endl;
        if(op==4) cout<<t.topk(t.root,x)<<endl;
        if(op==5) cout<<t.get_pre(x)<<endl;
        if(op==6) cout<<t.get_suc(x)<<endl;
    }
    return 0;
}


雙倍經驗之洛谷P6136

傳送錨點

題目簡述:

【模板】普通平衡樹(資料加強版)

題目背景

本題是 P3369 資料加強版,擴大資料範圍並增加了強制線上

題目的輸入、輸出和原題略有不同,但需要支援的操作相同。

題目描述

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

  1. 插入一個整數 \(x\)
  2. 刪除一個整數 \(x\)(若有多個相同的數,只刪除一個)。
  3. 查詢整數 \(x\) 的排名(排名定義為比當前數小的數的個數 \(+1\))。
  4. 查詢排名為 \(x\) 的數(如果不存在,則認為是排名小於 \(x\) 的最大數。保證 \(x\) 不會超過當前資料結構中數的總數)。
  5. \(x\) 的前驅(前驅定義為小於 \(x\),且最大的數)。
  6. \(x\) 的後繼(後繼定義為大於 \(x\),且最小的數)。

本題強制線上,保證所有操作合法(操作 \(2\) 保證存在至少一個 \(x\),操作 \(4,5,6\) 保證存在答案)。

輸入格式

第一行兩個正整數 \(n,m\),表示初始數的個數和操作的個數。

第二行 \(n\) 個整數 \(a_1,a_2,a_3,\ldots,a_n\),表示初始的數

接下來 \(m\) 行,每行有兩個整數 \(\text{opt}\)\(x'\)\(\text{opt}\) 表示操作的序號($ 1 \leq \text{opt} \leq 6 \(),\)x'$ 表示加密後的運算元。

我們記 \(\text{last}\) 表示上一次 \(3,4,5,6\) 操作的答案,則每次操作的 \(x'\) 都要異或\(\text{last}\) 才是真實的 \(x\)。初始 \(\text{last}\)\(0\)

輸出格式

輸出一行一個整數,表示所有 \(3,4,5,6\) 操作的答案的異或和

樣例 #1

樣例輸入 #1

6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 8
3 13
6 7
1 4

樣例輸出 #1

6

提示

樣例解釋

樣例加密前為:

6 7
1 1 4 5 1 4
2 1
1 9
4 1
5 9
3 8
6 1
1 0

限制與約定

對於 \(100\%\) 的資料,\(1\leq n\leq 10^5\)\(1\leq m\leq 10^6\)\(0\leq a_i,x\lt 2^{30}\)

本題輸入資料較大,請使用較快的讀入方式。


\(\text{upd 2022.7.22}\):新增加 \(9\) 組 Hack 資料。



思路還是一模一樣,這裡直接給到程式碼。

#include <cstdio>
#include <cstring>
#include <random>
using namespace std;

const int N=1e6+1e5+1;
unsigned long long seed=1;

std::mt19937 rnd(std::random_device{}());
struct fhq
{
    struct
    {
        int l,r;
        int val;
        int rnd;
        int size;
    }tr[N];
    int root=0,n=0;
    inline int _rand(){return rnd();}
    int newnode(int v)
    {
        tr[++n].val=v;
        tr[n].rnd=_rand();
        tr[n].size=1;
        return n;
    }
    void pushup(int x){tr[x].size=tr[tr[x].l].size+tr[tr[x].r].size+1;}
    void split(int rt,int v,int &x,int &y)
    {
        if(!rt) {x=y=0;return;}
        if(tr[rt].val<=v)
        {
            x=rt;
            split(tr[rt].r,v,tr[rt].r,y);
        }
        else
        {
            y=rt;
            split(tr[rt].l,v,x,tr[rt].l);
        }
        pushup(rt);
    }
    int merge(int x,int y)
    {
        if(!x||!y) return x+y;
        if(tr[x].rnd<tr[y].rnd)
        {
            tr[x].r=merge(tr[x].r,y);
            pushup(x);
            return x;
        }
        else
        {
            tr[y].l=merge(x,tr[y].l);
            pushup(y);
            return y;  
        }
       
    }
    explicit fhq(){memset(tr,0,sizeof tr);}
    int size(){return tr[root].size;}

    void insert(int v)
    {
        int x,y;
        split(root,v,x,y);
        int z=newnode(v);
        root=merge(merge(x,z),y);
    }

    void del(int v)
    {
        int x,y,z;
        split(root,v,x,z);
        split(x,v-1,x,y);
        y=merge(tr[y].l,tr[y].r);
        root=merge(merge(x,y),z);
    }

    int rank(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=tr[x].size+1;
        root=merge(x,y);
        return ans;
    }

    int topk(int rt,int k)
    {
        int lsz=tr[tr[rt].l].size;
        if(k==lsz+1) return tr[rt].val;
        if(k<=lsz) return topk(tr[rt].l,k);
        return topk(tr[rt].r,k-lsz-1);
    }

    int get_pre(int v)
    {
        int x,y;
        split(root,v-1,x,y);
        int ans=topk(x,tr[x].size);
        root=merge(x,y);
        return ans;
    }
    int get_suc(int v)
    {
        int x,y;
        split(root,v,x,y);
        int ans=topk(y,1);
        root=merge(x,y);
        return ans;
    }
};
int main(void)
{
    int n,m;
    scanf("%d%d",&n,&m);    
    fhq t;int w;
    for(int i=0;i<n;i++) 
    {
        scanf("%d", &w);
        t.insert(w);
    }
    int ans=0,last=0;
    while(m--)
    {
        int op,x;
        scanf("%d%d", &op, &x);
        x^=last;
        if(op==1) t.insert(x);
        if(op==2) t.del(x);
        if(op==3) {last=t.rank(x);ans^=last;}
        if(op==4) {last=t.topk(t.root,x);ans^=last;}
        if(op==5) {last=t.get_pre(x);ans^=last;}
        if(op==6) {last=t.get_suc(x);ans^=last;}
    }
    printf("%d\n", ans);
    return 0;
}

本蒟蒻只能寫到這。
謝謝!

相關文章