【10.13】FHQ-Treap

FHQ-Treap

一种无需旋转,只使用分裂、合并来维护一个Treap的方式。

参考blog:


原理

无旋Treap中每个点有真实值val和随机权值key
val保证Treap的平衡树性质(也就是每个节点左子树内节点真实权值小于它,右子树相反),
key满足大(小)根堆的性质,以其随机性维护树的平衡,使得期望树高在 \(logn\) 级别。


结构体/变量定义

std::mt19937 Rand(9023645);
struct D{
    int ch[2],val,key,size;
    void new_(int x){
        ch[0]=ch[1]=0; val=x; key=Rand(); size=1;
        return;
    }
}t[MAXN];
int root,cnt;
void pushup(int now){
    t[now].size=t[t[now].ch[0]].size+t[t[now].ch[1]].size+1;
    return;
}

重要操作

  • Merge(合并)

假设有两颗子树u,v,且 u 的所有节点的值都小于 v 的所有节点的值,随机权值 key 都以小根堆的形式存储。

int merge(int u,int v){
    if (!u || !v) return u|v; //空的直接返回
    if (t[u].key<t[v].key){ //此时u节点一定要放在v节点上面,所以将u的右子树与v的子树继续合并,合并后的根即为u的右儿子
        t[u].ch[1]=merge(t[u].ch[1],v);
        pushup(u); return u;
    } else { //反之同理
        t[v].ch[0]=merge(u,t[v].ch[0]);
        pushup(v); return v;
    }
}
  • Split(分裂)
  1. 按值split:

首先得有个基准值 x ,即权值小于等于 x 的节点全部进入左树,大于x的节点全部进入右树。

void split_val(int now,int x,int &u,int &v){
    if (!now) return (void)(u=v=0); //空树则返回空指针
    if (t[now].val<=x){ //节点值小于基准值,则他本身和他的左子树全部进入左树,右子树继续分裂,其分裂后小于基准值的部分成为这个节点的右子树,大于基准值的部分进入右树
        u=now; split_val(t[now].ch[1],x,t[now].ch[1],v);
    } else { //反之亦然
        v=now; split_val(t[now].ch[0],x,u,t[now].ch[0]);
    }
    pushup(now); //记得pushup
    return;
}
  1. 按排名split:

就是把前 rk 个节点拆入左树,其它节点拆入右树。原理与按值分裂差不多。

void split_rk(int now,int rk,int &u,int &v){
    if (!now) return (void)(u=v=0);
    if (t[t[now].ch[0]].size<rk){ //左子树的大小小于rk时,节点和左子树进入左树,右子树继续分裂
        u=now; split_rk(t[now].ch[1],rk-t[t[now].ch[0]].size-1,t[now].ch[1],v);
    } else {
        v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
    }
    pushup(now);
    return;
}

其他操作

  • 插入

插入权值为 x 的节点时,先新建一个节点,再以 x 为界按权值 split 整棵树为a,b,再按顺序 merge a,x,b。

void insert(int x){
    int a,b;
    split_val(root,x,a,b);
    t[++cnt].new_(x);
    root=merge(merge(a,cnt),b);
    return;
}
  • 删除

要删除x,先将整棵树以 x-1 为界按权值split 成a和b,再将 b 以 1 为界 按排名split 成c和d,则 c 就是要删除的节点。最后按顺序merge a,b,d。(如果x不存在则c的值不是x)

void del(int x){
    int a,b,c,d;
    split_val(root,x-1,a,b);
    split_rk(b,1,c,d);
    if (t[c].val==x) root=merge(a,d);
    else root=merge(merge(a,c),d);
    return;
}
  • 查询x的排名

先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。

int get_rk(int x){
    int a,b; int res;
    split_val(root,x-1,a,b);
    res=t[a].size+1;
    root=merge(a,b);
    return res;
}
  • 查询排名为k的值

先split出整棵树前k-1小节点,则右树最小节点即为所求节点,再次split 即可。

int to_be_rk(int rk){
    int a,b,c,d; int res;
    split_rk(root,rk-1,a,b);
    split_rk(b,1,c,d);
    res=t[c].val;
    root=merge(a,merge(c,d));
    return res;
}
  • 查x前驱

将整棵树以x-1按权值split,左树中最大节点即为所求节点。

int pre(int x){
    int a,b,c,d; int res;
    split_val(root,x-1,a,b);
    split_rk(a,t[a].size-1,c,d);
    res=t[d].val;
    root=merge(merge(c,d),b);
    return res;
}
  • 查x后继

将整棵树以x按权值split,右树中最小节点即为所求节点。

int suf(int x){
    int a,b,c,d; int res;
    split_val(root,x,a,b);
    split_rk(b,1,c,d);
    res=t[c].val;
    root=merge(a,merge(c,d));
    return res;
}

例题

  • P3369【模板】普通平衡树

您需要写一种数据结构,来维护一些数,其中需要提供以下操作:

  1. 插入一个数 \(x\)
  2. 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
  4. 查询数据结构中排名为 \(x\) 的数。
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

对于操作 3,5,6,不保证当前数据结构中存在数 \(x\)

\(1\le n \le 10^5\)\(|x| \le 10^7\)

Code

#include<bits/stdc++.h>
#define MAXN 100003
using namespace std;
int n;
std::mt19937 Rand(9023645);
struct D{
    int ch[2],val,key,size;
    void new_(int x){
        ch[0]=ch[1]=0; val=x; key=Rand(); size=1;
        return;
    }
}t[MAXN];
int root,cnt;
void pushup(int now){
    t[now].size=t[t[now].ch[0]].size+t[t[now].ch[1]].size+1;
    return;
}
int merge(int u,int v){
    if (!u || !v) return u|v;
    if (t[u].key<t[v].key){
        t[u].ch[1]=merge(t[u].ch[1],v);
        pushup(u); return u;
    } else {
        t[v].ch[0]=merge(u,t[v].ch[0]);
        pushup(v); return v;
    }
}
void split_val(int now,int x,int &u,int &v){
    if (!now) return (void)(u=v=0);
    if (t[now].val<=x){
        u=now; split_val(t[now].ch[1],x,t[now].ch[1],v);
    } else {
        v=now; split_val(t[now].ch[0],x,u,t[now].ch[0]);
    }
    pushup(now);
    return;
}
void split_rk(int now,int rk,int &u,int &v){
    if (!now) return (void)(u=v=0);
    if (t[t[now].ch[0]].size<rk){
        u=now; split_rk(t[now].ch[1],rk-t[t[now].ch[0]].size-1,t[now].ch[1],v);
    } else {
        v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
    }
    pushup(now);
    return;
}
void insert(int x){
    int a,b;
    split_val(root,x,a,b);
    t[++cnt].new_(x);
    root=merge(merge(a,cnt),b);
    return;
}
void del(int x){
    int a,b,c,d;
    split_val(root,x-1,a,b);
    split_rk(b,1,c,d);
    if (t[c].val==x) root=merge(a,d);
    else root=merge(merge(a,c),d);
    return;
}
int get_rk(int x){
    int a,b; int res;
    split_val(root,x-1,a,b);
    res=t[a].size+1;
    root=merge(a,b);
    return res;
}
int to_be_rk(int rk){
    int a,b,c,d; int res;
    split_rk(root,rk-1,a,b);
    split_rk(b,1,c,d);
    res=t[c].val;
    root=merge(a,merge(c,d));
    return res;
}
int pre(int x){
    int a,b,c,d; int res;
    split_val(root,x-1,a,b);
    split_rk(a,t[a].size-1,c,d);
    res=t[d].val;
    root=merge(merge(c,d),b);
    return res;
}
int suf(int x){
    int a,b,c,d; int res;
    split_val(root,x,a,b);
    split_rk(b,1,c,d);
    res=t[c].val;
    root=merge(a,merge(c,d));
    return res;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for (int i=1;i<=n;i++){
        int type,x; cin>>type>>x;
        if (type==1){
            insert(x);
        } else if (type==2){
            del(x);
        } else if (type==3){
            cout<<get_rk(x)<<endl;
        } else if (type==4){
            cout<<to_be_rk(x)<<endl;
        } else if (type==5){
            cout<<pre(x)<<endl;
        } else {
            cout<<suf(x)<<endl;
        }
    }
    return 0;
}
  • P3960 [NOIP2017 提高组] 列队

Solu

注意到区间值的连续性,考虑使用平衡树维护区间,支持分裂区间,快速加点、删点操作。

Code

常数过大,未AC

#include <bits/stdc++.h>
#define MAXN 300003
#define int long long
using namespace std;
int n,m,q;
struct eve{
    int x;
    int y;
    int id;
}e[MAXN];
std::mt19937 Rand(9879873);
struct D{
    int ch[2],key;
    long long l,r,size,len; //Treap内存的是区间
}t[MAXN*20];
int root[MAXN+1],cnt;
void pushup(int x) {t[x].size=t[x].len+t[t[x].ch[0]].size+t[t[x].ch[1]].size;}
int merge(int u,int v){
    if (!u || !v) return u|v;
    if (t[u].key<=t[v].key){
        t[u].ch[1]=merge(t[u].ch[1],v);
        pushup(u); return u;
    } else {
        t[v].ch[0]=merge(u,t[v].ch[0]);
        pushup(v); return v;
    }
}
void split_rk(int now,int rk,int &u,int &v){
    if (!now) return (void)(u=v=0);
    if (t[now].size-t[t[now].ch[1]].size<=rk){
        u=now; split_rk(t[now].ch[1],rk-(t[now].size-t[t[now].ch[1]].size),t[now].ch[1],v);
    } else {
        v=now; split_rk(t[now].ch[0],rk,u,t[now].ch[0]);
    }
    pushup(now);
    return;
}
void split_rk2(int now,int rk,int &u,int &v){ //倒着弄,使得包含第rk个数的区间在右子树内
    if (!now) return (void)(u=v=0);
    if (t[now].size-t[t[now].ch[0]].size<=rk){
        u=now; split_rk2(t[now].ch[0],rk-(t[now].size-t[t[now].ch[0]].size),t[now].ch[0],v);
    } else {
        v=now; split_rk2(t[now].ch[1],rk,u,t[now].ch[1]);
    }
    pushup(now);
    return;
}
void insert(int rtw,long long x){
    t[++cnt].l=x; t[cnt].r=x; t[cnt].len=t[cnt].size=1;
    t[cnt].key=Rand();
    root[rtw]=merge(root[rtw],cnt);
    return;
}
long long del(int rtw,int rk){
    int a,b,c,d; long long res;
    split_rk(root[rtw],rk-1,a,b);
    split_rk2(b,t[b].size-1,d,c); //找出包含第rk个数的区间c
    if (t[c].size==1) {res=t[c].l; t[c].len=t[c].size=0; root[rtw]=merge(a,d);}
    else {
        if (t[a].size+1==rk) {res=t[c].l; t[c].l++; t[c].len--; t[c].size--; root[rtw]=merge(merge(a,c),d);}
        else if (t[a].size+t[c].size==rk) {res=t[c].r; t[c].r--; t[c].len--; t[c].size--; root[rtw]=merge(merge(a,c),d);}
        else {
            res=t[c].l+(rk-t[a].size)-1;
            t[++cnt].l=t[c].l+(rk-t[a].size); t[cnt].r=t[c].r; t[cnt].size=t[cnt].len=t[cnt].r-t[cnt].l+1;
            t[c].r=t[c].l+(rk-t[a].size)-2; t[c].size=t[c].len=t[c].r-t[c].l+1;
            root[rtw]=merge(merge(merge(a,c),cnt),d);
        }
    }
    return res;
}
void init(){
    for (int i=1;i<=n+1;i++) root[i]=++cnt;
    for (int i=1;i<=n;i++){
        t[i].l=(i-1)*m+1; t[i].r=i*m-1;
        t[i].size=t[i].len=t[i].r-t[i].l+1;
        t[i].key=Rand();
    }
    for (int i=1;i<=n;i++) insert(n+1,1ll*i*m);
    return;
}
signed main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>q;
    for (int i=1;i<=q;i++) {cin>>e[i].x>>e[i].y;}
    init();
    for (int i=1;i<=q;i++){
        if (e[i].y!=m){
            long long val=del(e[i].x,e[i].y);
            insert(n+1,val);
            long long tmp=del(n+1,e[i].x);
            insert(e[i].x,tmp);
            cout<<val<<endl;
        } else {
            long long val=del(n+1,e[i].x);
            insert(n+1,val);
            cout<<val<<endl;
        }
    }
    return 0;
}

over.

posted @   EcapsXD  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示