1. 程式人生 > >Luogu P3835 【模板】可持久化平衡樹

Luogu P3835 【模板】可持久化平衡樹

P3835 【模板】可持久化平衡樹

題意

題目背景

本題為題目普通平衡樹的可持久化加強版。

題目描述

您需要寫一種資料結構(可參考題目標題),來維護一些數,其中需要提供以下操作(對於各個以往的歷史版本):

  1. 插入\(x\)
  2. 刪除\(x\)數(若有多個相同的數,因只刪除一個,如果沒有請忽略該操作)
  3. 查詢\(x\)數的排名(排名定義為比當前數小的數的個數\(+1\)。若有多個相同的數,因輸出最小的排名)
  4. 查詢排名為x的數
  5. \(x\)的前驅(前驅定義為小於\(x\),且最大的數,如不存在輸出\(-2147483647\))
  6. \(x\)的後繼(後繼定義為大於\(x\)
    ,且最小的數,如不存在輸出\(2147483647\))

和原本平衡樹不同的一點是,每一次的任何操作都是基於某一個歷史版本,同時生成一個新的版本。(操作\(3,4,5,6\)即保持原版本無變化)

每個版本的編號即為操作的序號(版本\(0\)即為初始狀態,空樹)

輸入輸出格式

輸入格式:

第一行包含一個正整數\(N\),表示操作的總數。

接下來每行包含三個整數,第\(i\)行記為\(v_i,opt_i,x_i\)

\(v_i\)表示基於的過去版本號\((0\leq v_i<i)\)\(opt_i\)表示操作的序號\((1\leq opt\leq 6)\)\(x_i\)表示參與操作的數值。

輸出格式:

每行包含一個正整數,依次為各個\(3,4,5,6\)操作所對應的答案

輸入輸出樣例

輸入樣例#1:

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

輸出樣例#1:

9
1
2
10
3

說明

資料範圍:

對於\(28%\)的資料滿足:\(1\leq n\leq 10\)

對於\(44%\)的資料滿足:\(1\leq n\leq 2\cdot {10}^2\)

對於\(60%\)的資料滿足:\(1\leq n\leq 3\cdot {10}^3\)

對於\(84%\)的資料滿足:\(1\leq n\leq {10}^5\)

對於\(92%\)的資料滿足:\(1\leq n\leq 2\cdot {10}^5\)

對於\(100%\)的資料滿足:\(1\leq n\leq 5\cdot {10}^5\)

經實測,正常常數的可持久化平衡樹均可通過,請各位放心

樣例說明:

\(10\)次操作,\(11\)個版本,各版本的狀況依次是:

  1. \([]\)
  2. \([9]\)
  3. \([3,9]\)
  4. \([9,10]\)
  5. \([3,9]\)
  6. \([9,10]\)
  7. \([2,9,10]\)
  8. \([2,9,10]\)
  9. \([2,10]\)
  10. \([2,10]\)
  11. \([3,9]\)

思路

\(fhq\ Treap\)天下第一! --Uranus

在可持久化的狀況下,\(fhq\ Treap\)的優越性被髮揮得淋漓盡致。總體函式完全沒有變化,只是多加了幾條新建點的語句:

int merge(int x,int y)
{
    if(!x||!y) return x+y;
    if(rnd(x)>rnd(y))
    {
        int p=++cnt;node[p]=node[x];//new
        rs(p)=merge(rs(p),y);
        update(p);
        return p;
    }
    else
    {
        int p=++cnt;node[p]=node[y];//new
        ls(p)=merge(x,ls(p));
        update(p);
        return p;
    }
}
void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(val(now)<=k)
        {
            x=++cnt;node[x]=node[now];//new
            split(rs(x),k,rs(x),y);
            update(x);
        }
        else
        {
            y=++cnt;node[y]=node[now];//new
            split(ls(y),k,x,ls(y));
            update(y);
        }
    }
}

上面程式碼標了new的就是新語句。

其他的操作完全相同,訪問歷史版本的操作也只需要對於每一個版本根不相同就可以了。

AC程式碼

#include<bits/stdc++.h>
using namespace std;
const int INF=2147483647;
const int MAXN=5e5+5;
int n,cnt,rt[MAXN];
struct fhq_Treap
{
    int sz,rnd,val;
    int ls,rs;
    #define sz(a) node[a].sz
    #define rnd(a) node[a].rnd
    #define val(a) node[a].val
    #define ls(a) node[a].ls
    #define rs(a) node[a].rs
}node[MAXN<<6];
int read()
{
    int re=0;bool f=true;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=false;ch=getchar();}
    while(isdigit(ch)) re=(re<<3)+(re<<1)+ch-'0',ch=getchar();
    return f?re:-re;
}
int newnode(int k)
{
    sz(++cnt)=1;
    rnd(cnt)=rand();
    val(cnt)=k;
    return cnt;
}
void update(int p){sz(p)=sz(ls(p))+sz(rs(p))+1;}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    if(rnd(x)>rnd(y))
    {
        int p=++cnt;node[p]=node[x];
        rs(p)=merge(rs(p),y);
        update(p);
        return p;
    }
    else
    {
        int p=++cnt;node[p]=node[y];
        ls(p)=merge(x,ls(p));
        update(p);
        return p;
    }
}
void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(val(now)<=k)
        {
            x=++cnt;node[x]=node[now];
            split(rs(x),k,rs(x),y);
            update(x);
        }
        else
        {
            y=++cnt;node[y]=node[now];
            split(ls(y),k,x,ls(y));
            update(y);
        }
    }
}
int kth(int now,int k)
{
    if(k==sz(ls(now))+1) return val(now);
    else if(k<=sz(ls(now))) return kth(ls(now),k);
    else return kth(rs(now),k-sz(ls(now))-1);
}
int main()
{
    srand(time(0));
    n=read();
    for(int i=1;i<=n;i++)
    {
        int ver=read(),opt=read(),k=read();rt[i]=rt[ver];
        if(opt==1)
        {
            int x,y;
            split(rt[i],k,x,y);
            rt[i]=merge(merge(x,newnode(k)),y);
        }
        else if(opt==2)
        {
            int x,y,z;
            split(rt[i],k,x,z);
            split(x,k-1,x,y);
            y=merge(ls(y),rs(y));
            rt[i]=merge(merge(x,y),z);
        }
        else if(opt==3)
        {
            int x,y;
            split(rt[i],k-1,x,y);
            printf("%d\n",sz(x)+1);
            rt[i]=merge(x,y);
        }
        else if(opt==4) printf("%d\n",kth(rt[i],k));
        else if(opt==5)
        {
            int x,y;
            split(rt[i],k-1,x,y);
            if(!x) printf("%d\n",-INF);
            else printf("%d\n",kth(x,sz(x)));
            rt[i]=merge(x,y);
        }
        else if(opt==6)
        {
            int x,y;
            split(rt[i],k,x,y);
            if(!y) printf("%d\n",INF);
            else printf("%d\n",kth(y,1));
            rt[i]=merge(x,y);
        }
    }
    return 0;
}