Treap树 ← 洛谷P3369、AcWing253

【题目来源】
https://www.luogu.com.cn/problem/P3369
https://www.acwing.com/problem/content/255/

【题目描述】
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:
1. 插入数值 x。
2. 删除数值 x(若有多个相同的数,应只删除一个)。
3. 查询数值 x 的排名(若有多个相同的数,应输出最小的排名)。
4. 查询排名为 x 的数值。
5. 求数值 x 的前驱(前驱定义为小于 x 的最大的数)。
6. 求数值 x 的后继(后继定义为大于 x 的最小的数)。
注意: 数据保证查询的结果一定存在。

【输入格式】
第一行为 n,表示操作的个数。
接下来 n 行每行有两个数 opt 和 x,opt 表示操作的序号(1≤opt≤6)。

【输出格式】
对于操作 3,4,5,6 每行输出一个数,表示对应答案。

【数据范围】
1≤n≤100000,所有数均在 −10^7 到 10^7 内。

【输入样例】
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

【输出样例】
106465
84185
492737

【算法分析】
● Treap 树是一种原理比较简单的 BST。
Treap=Tree+Heap,即 Treap 是树和堆(大根堆或小根堆)的结合,通常翻译成树堆
● Treap 树的操作基于“
键值 val + 优先级 pri”。Treap 树的每个结点包含两个信息 (val, pri),对于 val,它是一个 BST(二叉查找树),对于 pri,它是一个(大根堆或小根堆)。注意这条性质与笛卡尔树的异同。
● Treap 树的核心思想就是利用优先级维护二叉树的平衡性。
● 针对 Treap 树,由于每个结点的(val,pri)可看作结点在平面上的坐标,故若每个结点的键值 val,优先级 pri 已经
事先确定且不同,那么建立的 Treap 树的形态是唯一的,与结点的插入顺序没有关系。Treap 树中结点的键值 val 确定了结点在 Treap 树中的横向位置优先级 pri 确定了结点在 Treap 树中的纵向位置
给定键值
{a,b,c,d,e,f,g},优先级{6,5,2,7,3,4,1},构建的 Treap 树如下图所示:

● 需要注意,
如果预先知道所有结点的键值 val,那么在构建 Treap 树时,先对所有的键值 val 进行递增排序,然后从键值 val 最小的结点开始,从左至右逐个向树上添加结点,并依据优先级(已知或随机)在纵向上调整形态。此种情形下,构建的 Treap 树就是笛卡尔树。建树的时间复杂度为 O(n)。
而在
一般情况下,并不能预先知道结点的键值 val 和优先级 pri,做法是为读入的每个新键值 val 分配一个随机的优先级,在插入树中时动态调整树的结构,使它仍然是一棵 Treap 树。建树的时间复杂度为 O(log_2n)
● 动态调整 Treap 树的结构,有两种方法:
旋转法FHQ 法。旋转法是经典方法,FHQ 法是近几年开始流行的新技术。FHQ 法编码简单,且能用于区间翻转、移动、持久化场合。 

【算法代码】

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
int root,idx;

struct node {
    int le,ri;
    int val,pri;
    int cnt,size;
} tr[maxn];

void pushup(int p) { //update size
    tr[p].size=tr[tr[p].le].size+tr[tr[p].ri].size+tr[p].cnt;
}

int get_node(int val) { //build node
    tr[++idx].val=val;
    tr[idx].pri=rand();
    tr[idx].cnt=tr[idx].size=1;
    return idx;
}

void zig(int &p) { //rotate to right
    int q=tr[p].le;
    tr[p].le=tr[q].ri;
    tr[q].ri=p;
    p=q;
    pushup(tr[p].ri);
    pushup(p);
}

void zag(int &p) { //rotate to left
    int q=tr[p].ri;
    tr[p].ri=tr[q].le;
    tr[q].le=p;
    p=q;
    pushup(tr[p].le);
    pushup(p);
}

void build() {
    get_node(-inf);
    get_node(inf);
    root=1;
    tr[1].ri=2;
    pushup(root);
    if(tr[1].pri<tr[2].pri) zag(root);
}

void insert(int &p,int val) {
    if(!p) p=get_node(val);
    else if(tr[p].val==val) tr[p].cnt++;
    else if(tr[p].val>val) {
        insert(tr[p].le,val);
        if(tr[tr[p].le].pri>tr[p].pri) zig(p);
    } else {
        insert(tr[p].ri,val);
        if(tr[tr[p].ri].pri>tr[p].pri) zag(p);
    }
    pushup(p);
}

void erase(int &p,int val) {
    if(!p) return;
    if(tr[p].val==val) {
        if(tr[p].cnt>1) tr[p].cnt--;
        else if(tr[p].le || tr[p].ri) {
            if(!tr[p].ri || tr[tr[p].le].pri>tr[tr[p].ri].pri) {
                zig(p);
                erase(tr[p].ri,val);
            } else {
                zag(p);
                erase(tr[p].le,val);
            }
        } 
        else p=0;
    } 
    else if(tr[p].val>val) erase(tr[p].le,val);
    else erase(tr[p].ri,val);
    pushup(p);
}

int get_rank_by_val(int p,int val) {
    if(!p) return 0;
    if(tr[p].val==val) return tr[tr[p].le].size+1;
    if(tr[p].val>val) return get_rank_by_val(tr[p].le,val);
    return tr[tr[p].le].size+tr[p].cnt+get_rank_by_val(tr[p].ri,val);
}

int get_val_by_rank(int p,int rank) {
    if(!p) return 0;
    if(tr[tr[p].le].size>=rank) return get_val_by_rank(tr[p].le,rank);
    if(tr[tr[p].le].size+tr[p].cnt>=rank) return tr[p].val;
    return get_val_by_rank(tr[p].ri,rank-tr[tr[p].le].size-tr[p].cnt);
}

int get_prev(int p,int val) {
    if(!p) return -inf;
    if(tr[p].val>=val) return get_prev(tr[p].le,val);
    return max(tr[p].val,get_prev(tr[p].ri,val));
}

int get_next(int p,int val) {
    if(!p) return inf;
    if(tr[p].val<=val) return get_next(tr[p].ri,val);
    return min(tr[p].val,get_next(tr[p].le,val));
}

int main() {
    build();
    int n;
    scanf("%d",&n);
    while(n--) {
        int op,x;
        scanf("%d %d",&op,&x);
        if(op==1) insert(root,x);
        else if(op==2) erase(root,x);
        else if(op==3) printf("%d\n",get_rank_by_val(root,x)-1);
        else if(op==4) printf("%d\n",get_val_by_rank(root,x+1));
        else if(op==5) printf("%d\n",get_prev(root,x));
        else printf("%d\n",get_next(root,x));
    }
    return 0;
}

/*
in:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

out:
106465
84185
492737
*/


本题利用替罪羊树的代码解法如下:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;
const double alpha=0.7;

struct node {
    int le,ri;
    int cnt,val,size;
} sgt[maxn];

int n,m;
int cnt=1;

/*
tp 表示子树中结点下标组成的线性序列
tn 表示子树中结点个数组成的线性序列
tv 表示子树中结点权值组成的线性序列
*/
vector<int> tp,tn,tv;

bool exist(int x) {
    return !(sgt[x].le==0 && sgt[x].ri==0 && sgt[x].cnt==0);
}

int flatten(int x) {
    if(exist(sgt[x].le)) flatten(sgt[x].le);
    int id=tp.size();
    if(sgt[x].cnt) {
        tp.push_back(x);
        tn.push_back(sgt[x].cnt);
        tv.push_back(sgt[x].val);
    }
    if(exist(sgt[x].ri)) flatten(sgt[x].ri);
    return id;
}

void rebuild(int x,int le,int ri) {
    int mid=(le+ri)/2;
    int sizel=0,sizer=0;
    if(le<mid) {
        sgt[x].le=tp[(le+mid-1)/2];
        rebuild(sgt[x].le,le,mid-1);
        sizel=sgt[sgt[x].le].size;
    } else sgt[x].le=0;
    if(ri>mid) {
        sgt[x].ri=tp[(mid+1+ri)/2];
        rebuild(sgt[x].ri,mid+1,ri);
        sizer=sgt[sgt[x].ri].size;
    } else sgt[x].ri=0;
    sgt[x].cnt=tn[mid];
    sgt[x].val=tv[mid];
    sgt[x].size=sizel+sizer+sgt[x].cnt;
}

void restr(int x) {
    double val=max(sgt[sgt[x].le].size, sgt[sgt[x].ri].size)*1.0/sgt[x].size;
    if(val>alpha) {
        tp.clear();
        tn.clear();
        tv.clear();
        int id=flatten(x);
        swap(tp[id],tp[(tp.size()-1)/2]);
        rebuild(x,0,tp.size()-1);
    }
}

void insert(int x, int val) {
    if(!exist(x)) {
        sgt[x].cnt=1;
        sgt[x].val=val;
    } else if(val<sgt[x].val) {
        if(!exist(sgt[x].le)) sgt[x].le=++cnt;
        insert(sgt[x].le,val);
    } else if(val>sgt[x].val) {
        if (!exist(sgt[x].ri)) sgt[x].ri=++cnt;
        insert(sgt[x].ri,val);
    } else sgt[x].cnt++;
    sgt[x].size++;
    restr(x);
}

void del(int x, int val) {
    sgt[x].size--;
    if(val<sgt[x].val) del(sgt[x].le,val);
    else if (val>sgt[x].val) del(sgt[x].ri,val);
    else sgt[x].cnt--;
    restr(x);
}

int ord_pre(int x, int val) {
    if(val<sgt[x].val)
        return (exist(sgt[x].le)?ord_pre(sgt[x].le,val):0);
    else if(val>sgt[x].val)
        return sgt[sgt[x].le].size+sgt[x].cnt+(exist(sgt[x].ri)?ord_pre(sgt[x].ri,val):0);
    else
        return sgt[sgt[x].le].size;
}

int ord_nxt(int x, int val) {
    if(val>sgt[x].val)
        return ((exist(sgt[x].ri)?ord_nxt(sgt[x].ri,val):0));
    else if(val<sgt[x].val)
        return sgt[sgt[x].ri].size+sgt[x].cnt+(exist(sgt[x].le)?ord_nxt(sgt[x].le,val):0);
    else
        return sgt[sgt[x].ri].size;
}

int ord(int val) {
    return ord_pre(1,val)+1;
}

int find(int x, int rk) {
    if(rk<=sgt[sgt[x].le].size)
        return find(sgt[x].le, rk);
    else if(rk>sgt[sgt[x].le].size+sgt[x].cnt)
        return find(sgt[x].ri, rk-sgt[sgt[x].le].size-sgt[x].cnt);
    else
        return sgt[x].val;
}

int pre(int x) {
    int rk=ord_pre(1,x);
    return find(1,rk);
}

int nxt(int x) {
    int rk=ord_nxt(1,x);
    return find(1,sgt[1].size-rk+1);
}

int main() {
    int opt,x;
    scanf("%d",&n);
    for(int i=1; i<=n; i++) {
        scanf("%d%d",&opt,&x);
        if(opt==1) insert(1,x);
        else if(opt==2) del(1,x);
        else if(opt==3) printf("%d\n",ord(x));
        else if(opt==4) printf("%d\n",find(1,x));
        else if(opt==5) printf("%d\n",pre(x));
        else printf("%d\n",nxt(x));
    }
    return 0;
}


/*
in:
10
1 106465
4 1
1 317721
1 460929
1 644985
1 84185
1 89851
6 81968
1 492737
5 493598

out:
106465
84185
492737
*/




【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/120380473
https://www.acwing.com/solution/content/153478/






 

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值