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. GRASP软件设计的模式和原则

    GRASP 模式:每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.”这是关于模式最经典的定义,作者是建筑大师Christopher Alexander.如果是第一次看到这 ...

  2. GIT的前世今生

    在重点介绍GIT的一些操作之前,我们首先来说一说GIT的前世今生,了解整个版本控制的变迁能够让我们知道该如何去选择这些工具,另外通过这些技术的变迁也能够让我们对现在的技术有着更加深入的理解,在正式介绍 ...

  3. 重写TreeView模板来实现数据分层展示(一)

    总想花些时间来好好总结一下TreeView这个WPF控件,今天来通过下面的这几个例子来好好总结一下这个控件,首先来看看一个常规的带虚线的TreeView控件吧,在介绍具体如何完成之前首先来看看最终实现 ...

  4. 在Hmtl页面中只让其中单独的一个div隐藏滚动条但是仍可滚动浏览下边的内容

    <style> .box ::-webkit-scrollbar {width: 0px;} </style> <div class="box"> ...

  5. Linux中,去掉终端显示的当前目录的绝对路径

    Linux中,去掉终端显示的当前目录的绝对路径 去~/.bashrc中,找到PS1变量的定义,如果没有,手动加上: 可以将显示输出到标题栏上: #export PS1="[e]2;u@H w ...

  6. How to ssh

    ssh -p 22 cuthead@127.0.0.1

  7. Spring MVC 使用介绍(一)—— 概述

    一.Web MVC简介 1.经典的MVC架构 存在的问题:1.控制器负责流程控制.请求数据整理与校验.模型与视图选择等功能,过于复杂.2.模型层没有进行分层设计 2.改进的MVC设计 1)控制器功能拆 ...

  8. 在idea中设置记住git的用户名和密码

    在idea中设置记住git的用户名和密码 1.在项目根目录下执行以下git命令: git config --global credential.helper store 2.执行上述命令后,在idea ...

  9. BZOJ5037[Jsoi2014]电信网络——最大权闭合子图

    题目描述 JYY创建的电信公司,垄断着整个JSOI王国的电信网络.JYY在JSOI王国里建造了很多的通信基站.目前所有的基站 都是使用2G网络系统的.而现在3G时代已经到来了,JYY在思考,要不要把一 ...

  10. BZOJ2463[中山市选2009]谁能赢呢?——博弈论

    题目描述 小明和小红经常玩一个博弈游戏.给定一个n×n的棋盘,一个石头被放在棋盘的左上角.他们轮流移动石头.每一回合,选手只能把石头向上,下,左,右四个方向移动一格,并且要求移动到的格子之前不能被访问 ...