0. 前置知识:\(treap\)的定义

树堆,在数据结构中也称Treap,是指有一个随机附加域满足堆的性质的二叉搜索树,其结构相当于以随机数据插入的二叉搜索树。
​ >——摘自百度百科

形象化一点:

  • \(treap\)是关于\(val\)的二叉搜索树
  • \(treap\)是关于\((随机权值)rdm(随机权值)\)的二叉搜索树
    为了满足上面两条性质,就要分情况对\(treap\)进行旋转
    怎么转我也不会……因为这是介绍\(fhq\) \(treap\)的文章

1.\(fhq\) \(treap\)

基本定义&操作

解释下数组含义

int son[N][2]; //0:左儿子 1:右儿子
int sz[N];//子树大小
int val[N];//权值
int rdm[N];//随机权值
int cnt;//总treap的节点个数

如何开一个新节点

int new_node(int x) {
    val[++cnt]=x;//节点权值
    rdm[cnt]=rand();//随机权值
    sz[cnt]=1;//子树大小
    return cnt;
}

\(pushup\)

inline void pushup(int x) {
    sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;
}

核心操作

\(fhq\) \(treap\)的核心操作只有两种:\(split\)和\(merge\),这两种操作巧妙玄学地让每次插入和删除,\(treap\)的性质都不会被破坏,自然也就不用旋转

\(merge\)

把a、b两棵\(treap\)合成一棵大\(treap\)的操作
满足a所有节点的权值 < b所有节点的权值
这样就只用维护随机权值的\(heap\)性质

代码:

void merge(int &now,int x,int y) { //分别为根节点地址,a,b
    if(!x || !y) {
        now=x+y;
        return;
    }
    if(rdm[x]<rdm[y]) { //维护rdm堆
        now=x;//保留a的左子树
        merge(son[now][1],son[now][1],y);//把b塞进a的右子树,维护bst
    }else { //原理同上
        now=y;
        merge(son[now][0],x,son[now][0]);
    }
    pushup(now);
}

\(split\)

把一棵大\(treap\)按照某种划分标准分成\(a\)、\(b\)两棵\(treap\)的操作

常用的划分标准有权值\(val\)和子树大小\(sz\),两种代码都会给

代码:

  • 按val划分
//划分后,a中所有元素<=k,b中所有元素>k
void split(int now,int k,int &x,int &y) {//分别为:当前节点,划分标准,a的树根,b的树根
    if(!now) {//没东西了
        x=y=0;
        return;
    }
    if(val[now]<=k) {//利用bst的性质
        x=now;
        split(son[now][1],k,son[now][1],y);
    }else {
        y=now;
        split(son[now][0],k,x,son[now][0]);
    }
    pushup(now);
}
  • 按sz划分
//把大treap中前k小的元素放到a中,剩下放到b中
void split(int now,int k,int &x,int &y) {
    if(!now) {//没东西了
        x=y=0;
        return;
    }
    if(sz[son[now][0]]>k) {
        x=now;
        split(son[now][1],k-sz[son[now][0]]-1,son[now][1],y);
    }else {
        y=now;
        split(son[now][0],k,x,son[now][0]);
    }
    pushup(now);
}

2.模板题

洛谷 3369/BZOJ 3224/Tyvj 1728 普通平衡树

#include <bits/stdc++.h>
#define N 100005

using namespace std;

int root=0;

struct fhq_treap {
    int son[N][2],sz[N],val[N],rdm[N],cnt;

    inline int new_node(int x) {
        val[++cnt]=x,rdm[cnt]=rand(),sz[cnt]=1;
        return cnt;
    }

    inline void pushup(int x) {
        sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;
    }

    void split(int now,int k,int &x,int &y) {
        if(!now) {
            x=y=0;
            return;
        }
        if(val[now]<=k) {
            x=now;
            split(son[now][1],k,son[now][1],y);
        }else {
            y=now;
            split(son[now][0],k,x,son[now][0]);
        }
        pushup(now);
    }

    void merge(int &now,int x,int y) {
        if(!x || !y) {
            now=x+y;
            return;
        }
        if(rdm[x]<rdm[y]) {
            now=x;
            merge(son[now][1],son[now][1],y);
        }else {
            now=y;
            merge(son[now][0],x,son[now][0]);
        }
        pushup(now);
    }

    void kth(int now,int k) {//第k大数
        while(1) {
            if(sz[son[now][0]]>=k) {
                now=son[now][0];
            }else {
                if(sz[son[now][0]]+1==k) break;
                k-=(sz[son[now][0]]+1);
                now=son[now][1];
            }
        }
        printf("%d\n",val[now]);
    }
}T;

int main() {
    int n,op,x,a,b,c;
    scanf("%d",&n);
    T.sz[0]=0;
    T.cnt=0;
    while(n--) {
        a=0,b=0;
        scanf("%d%d",&op,&x);
        if(op==1) { //插入x数
            T.split(root,x,a,b);
            int t=T.new_node(x);
            T.merge(a,a,t);
            T.merge(root,a,b);
        }
        if(op==2) { //删除x数
            T.split(root,x,a,b);
            T.split(a,x-1,a,c);
            T.merge(c,T.son[c][0],T.son[c][1]);
            T.merge(a,a,c);
            T.merge(root,a,b);
        }
        if(op==3) { //查询x数的排名
            T.split(root,x-1,a,b);
            printf("%d\n",T.sz[a]+1);
            T.merge(root,a,b);
        }
        if(op==4) { //查询排名为x的数
            T.kth(root,x);
        }
        if(op==5) { //求x的前驱
            T.split(root,x-1,a,b);
            T.kth(a,T.sz[a]);
            T.merge(root,a,b);
        }
        if(op==6) { //求x的后继
            T.split(root,x,a,b);
            T.kth(b,1);
            T.merge(root,a,b);
        }
    }
}

洛谷 3391 文艺平衡树

这题只有区间翻转操作,所以不用考虑那么多有的没的.

可以把每个点的编号看作权值。假设要翻转区间\([l,r]\),先把\([l,r]\)这个区间分裂出来,打个标记,下传维护时直接\(swap(lson,rson)\)就行了

然而这样会破坏\(treap\)的性质,所以还有区间\(k\)大的话不能直接查

能写\(splay\)尽量写\(splay\)吧,\(fhq\) \(treap\)常数有点大……

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

int root=0;
struct fhq_treap{
    #define lson son[now][0]
    #define rson son[now][1]
    int rdm[N],val[N],w[N],sz[N],son[N][2],cnt=0;
    bool tag[N];//翻转的lazytag
    int NewNode(){
        val[++cnt]=cnt,rdm[cnt]=rand(),sz[cnt]=1,tag[cnt]=0;
        return cnt;
    }
    void pushup(int now){sz[now]=sz[lson]+sz[rson]+1; }
    void pushdown(int now){
        swap(lson,rson);
        if(lson) tag[lson]^=1;
        if(rson) tag[rson]^=1;
        tag[now]=0;
    }
    void merge(int &now,int x,int y){
        if(!x || !y){
            now=x+y;
            return;
        }
        if(rdm[x]<rdm[y]){
            if(tag[x]) pushdown(x);
            now=x,merge(rson,rson,y);
        }
        else{
            if(tag[y]) pushdown(y);
            now=y,merge(lson,x,lson);
        }
        pushup(now);
    }
    void split(int now,int k,int &x,int &y){
        if(!now){
            x=y=0;
            return;
        }
        if(tag[now]) pushdown(now);
        if(sz[lson]+1<=k) x=now,split(rson,k-sz[lson]-1,rson,y);
        else y=now,split(lson,k,x,lson);
        pushup(now);
    }
    void print(int now){//中序遍历输出答案
        if(!now) return;
        if(tag[now]) pushdown(now);
        print(lson);
        printf("%d ",val[now]);
        print(rson);
    }
}T;

int main(){
    int n,m,l,r,i;
    int a=0,b=0,c=0;
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        int x=T.NewNode();
        T.merge(root,root,x);
    }
    scanf("%d",&m);
    while(m--){
        a=0,b=0,c=0;
        scanf("%d%d",&l,&r);
        T.split(root,l-1,a,b);
        T.split(b,r-l+1,b,c);
        T.tag[b]^=1;
        T.merge(b,b,c),T.merge(root,a,b);
    }
    T.print(root);
}

学习笔记:fhq-treap的更多相关文章

  1. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  2. [学习笔记] 平衡树——Treap

    前置技能:平衡树前传:BST 终于学到我们喜闻乐见的平衡树啦! 所以我们这次讲的是平衡树中比较好写的\(Treap\). (以后会写splay的先埋个坑在这) 好了,进入正题. step 1 我们知道 ...

  3. fhq treap 学习笔记

    序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...

  4. fhq treap最终模板

    新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...

  5. FHQ treap学习(复习)笔记

    .....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...

  6. 「FHQ Treap」学习笔记

    话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...

  7. 「学习笔记」 FHQ Treap

    FHQ Treap FHQ Treap (%%%发明者范浩强年年NOI金牌)是一种神奇的数据结构,也叫非旋Treap,它不像Treap zig zag搞不清楚(所以叫非旋嘛),也不像Splay完全看不 ...

  8. Fhq Treap [FhqTreap 学习笔记]

    众所周知 Fhq Treap 是 fhq 神仙研究出来的平衡树- 具体实现 每个点实现一个 \(\text{rnd}\) 表示 rand 的值 为什么要 rand 呢 是为了保证树高为 \(\log ...

  9. 平衡树学习笔记(2)-------Treap

    Treap 上一篇:平衡树学习笔记(1)-------简介 Treap是一个玄学的平衡树 为什么说它玄学呢? 还记得上一节说过每个平衡树都有自己的平衡方式吗? 没错,它平衡的方式是......rand ...

  10. 左偏树 / 非旋转treap学习笔记

    背景 非旋转treap真的好久没有用过了... 左偏树由于之前学的时候没有写学习笔记, 学得也并不牢固. 所以打算写这么一篇学习笔记, 讲讲左偏树和非旋转treap. 左偏树 定义 左偏树(Lefti ...

随机推荐

  1. storm问题汇总

    1.删除了本地topology导致无法启动nimbus 删除storm的自定义的库中的数据 删除zookeeper中配置的dataDir中的数据 重启服务即可

  2. Satis搭建composer私有库(自定义下载目录)

    在我们的日常php开发中需要使用大量的第三方包和类库, 怎么管理是一个问题, 我们用的Yii2框架, 但是并没有把composer用起来, 由于最近更换为docker部署项目, 于是想起来用compo ...

  3. JS刷新当前页面的几种方法总结

    reload 方法,该方法强迫浏览器刷新当前页面. 语法:location.reload([bForceGet]) 参数: bForceGet, 可选参数, 默认为 false,从客户端缓存里取当前页 ...

  4. Hbase获取流程

    1\\.客户端chou操作 2.服务器dauncaozuo操作 3\存储优化

  5. python爬虫scrapy之登录知乎

    下面我们看看用scrapy模拟登录的基本写法: 注意:我们经常调试代码的时候基本都用chrome浏览器,但是我就因为用了谷歌浏览器(它总是登录的时候不提示我用验证码,误导我以为登录时不需要验证码,其实 ...

  6. pycharm 安装

    pycharm 1.模板 file->setting->Editor->file and code template->python script->右上方 #!/usr ...

  7. PLSQL 汉化

    自动导入PLSQL安装目录: 一直下一步就可以了: 之后重新打开:

  8. 导出数据到EXL表格中

    项目使用的是SSI框架,通过struts访问到action xml文件: <action name="fabAttributedaochu" class="com. ...

  9. Hack You CTF 2014: NotEasyTask

    exe文件,运行后闪退,peid查一下,发现是是c#写的 Reflector打开 找到Main: private static void Main(string[] args) { string ho ...

  10. 如何强制关闭LODOP或c-lodop已经弹出的预览窗口

    该文介绍一下LODOP和C-LODOP关于窗口的弹出,和如何强制关闭已经打开的预览窗口. 同一个页面,只能弹出一个窗口,lodop是禁止点击动作,而c-lodop会提示已有窗口开的,请关闭之类的默认提 ...