前言

原文写于 XJ 集训day2 2020.1.19.

现在想想那时候连模板都还没写,只是刚刚理解就在那里瞎yy……之前果然还是太幼稚了。

今天刷训练指南发现全是 Treap 和 Splay ,不想写 所以就来重写这个坑。

普通平衡树模板

基本思想:通过对平衡树进行分割(split)和合并(merge)完成维护平衡。

基本操作:插入,删除,查询排名,查询数值,求前驱,求后继

高级操作:可持久化,区间操作

显然我们是要维护一组数据。对于这组数据,每个点的值就是这棵平衡树的点权。根据基本性质,从点权角度看这棵树,满足二叉搜索树(BST)

然后 FHQ-Treap 还有一个附加值来维护平衡,这个值通常是随机出来的,且整棵树的附加值满足堆的性质。

有一说一,FHQ这样函数封装的是真的舒服……那我就直接讲函数好了。

模板题

1. 基本操作

  1. struct FHQTreap
  2. {
  3. int l,r,val,rnd,siz;
  4. }tr[N];
  5. int cnt=0,rt=0,tx,ty,tz;
  6. void update( int x ) //用左右子树的大小更新父节点子树大小
  7. {
  8. tr[x].siz=1+tr[tr[x].l].siz+tr[tr[x].r].siz;
  9. }
  10. int new_node( int val ) //新建一个节点(或者说 Treap )
  11. {
  12. tr[++cnt].siz=1; tr[cnt].val=val; tr[cnt].rnd=rand(); return cnt;
  13. }

1. Split

这个函数用途是按照某个方式分割一棵 Treap.

首先是按照权值的写法:

如果当前点的权值不大于 k ,根据二叉搜索树的性质,答案节点肯定在右子树里面。那么就将当前节点给分裂之后的“左树”\(x\) ,递归分割右子树。

如果当前节点权值大于 k,那么就同理赋值并递归分割左子树。

最后不要忘记 update。

  1. void split( int now,int k,int &x,int &y )
  2. { //split a Treap by val
  3. if ( !now ) { x=y=0; return; }
  4. if ( tr[now].val<=k ) x=now,split( tr[now].r,k,tr[now].r,y );
  5. //now比k小,左子树+根节点→x,递归分割右子树
  6. else y=now,split( tr[now].l,k,x,tr[now].l );
  7. update( now );
  8. }

然后是按照排名的写法:

如果 k 不大于左子树大小,那么就是在左子树或者根节点,递归分割左子树;否则递归分割右子树,不过排名 k 要减去左子树大小和根节点。

  1. void split( int now,int k,int &x,int &y )
  2. {
  3. if ( !now ) { x=y=0; return; }
  4. if ( k<=tr[tr[now].l].siz ) y=now,split( tr[now].l,k,x,tr[now].l );
  5. else x=now,split( tr[now].r,k-tr[tr[now].l].size-1,tr[now].r,y );
  6. update( now );
  7. }

2. Merge

顾名思义,就是合并两棵 Treap.

首先要保证 (x,y) 的顺序满足二叉搜索树的性质,所以merge的时候要注意顺序。

其次还要考虑附加值的堆性质。如果 \(x\) 的 rnd 比 \(y\) 大,那么说明在 “堆” 里面 \(x\) 应该是根节点,所以就递归,把 \(x\) 的右子树和 \(y\) 合并并更新 \(x\) .反之亦然。

  1. int merge( int x,int y )
  2. {
  3. if ( !x || !y ) return x+y;
  4. if ( tr[x].rnd<tr[y].rnd )
  5. { //make y -> x's right child
  6. tr[x].r=merge( tr[x].r,y ); update( x ); return x;
  7. }
  8. else
  9. { //make x -> y's left child
  10. tr[y].l=merge( x,tr[y].l ); update( y ); return y;
  11. }
  12. }

3. Kth

相当于一个查询操作,询问第 k 个权值。

如果当前的 k 不大于左子树大小,那么就递归左子树;

如果正好等于左子树+1(也就是比它小的数的个数+1,就是它本身),那么就算找到了,返回;

否则和排名 split 类似,减去左子树大小和根节点,递归右子树。

这里采用 while 替代递归。

  1. int get_kth( int now,int k )
  2. {
  3. while ( 1 )
  4. {
  5. if ( k<=tr[tr[now].l].siz ) now=tr[now].l;
  6. //k<=tr[l].size,the node must be in the left subtree
  7. else if ( k==tr[tr[now].l].siz+1 ) return now;
  8. //the kth node is exactly tree 'now'
  9. else k-=tr[tr[now].l].siz+1,now=tr[now].r;
  10. //k-=left subtree's size and 1 for the root,then search in the right subtree
  11. }
  12. }

4. 基本查询

插入

按照权值分割,然后合并左树和新节点,再和右树合并。

  1. if ( opt==1 ) //insert
  2. {
  3. split( rt,x,tx,ty ); rt=merge( merge(tx,new_node(x)),ty );
  4. }

删除

先按照权值 \(x\) 分割,然后再把小于 \(x\) 的部分分割出来,对于剩下的就是等于 \(x\) 的部分。这部分由于是删除一个节点(但是实际会有多个重复的点),所以直接忽略根节点合并左右子树即可。最后再和之前分出来的两部分合并,注意顺序。

(其实还有一种方法是同时在一个节点里面记录出现次数,删除减一即可)

(重复节点对 FHQ 是没有大问题的……但是对于伸展树嘛……初始节点一定要放啊……)

  1. if ( opt==2 ) //delete
  2. {
  3. split( rt,x,tx,tz ); split( tx,x-1,tx,ty );
  4. ty=merge( tr[ty].l,tr[ty].r ); rt=merge( merge(tx,ty),tz );
  5. }

根据值求排名

分割出左子树,然后左子树 siz +1 即可(原因自己读题)

  1. if ( opt==3 ) //get rank by val
  2. {
  3. split( rt,x-1,tx,ty ); printf( "%d\n",tr[tx].siz+1 ); rt=merge( tx,ty );
  4. }

Kth

  1. if ( opt==4 ) printf( "%d\n",tr[get_kth(rt,x)].val ); //get val by rank

求前驱

  1. if ( opt==5 ) //get pre node
  2. {
  3. split( rt,x-1,tx,ty );
  4. printf( "%d\n",tr[get_kth(tx,tr[tx].siz)].val );
  5. rt=merge( tx,ty );
  6. }

求后继

  1. if ( opt==6 ) //get suf node
  2. {
  3. split( rt,x,tx,ty );
  4. printf( "%d\n",tr[get_kth(ty,1)].val );
  5. rt=merge( tx,ty );
  6. }

文艺平衡树

模板题

题目要求是翻转区间,意思就是要支持区间操作(

代码可以直接在普通平衡树上面修改得到,这里只放一些不同的部分。

0. 【Important】Reverse

针对这道题的区间翻转操作,就是对 tag 进行修改。

主要这里涉及到了如何把一个区间抠出来进行操作。

思想很简单,就是把右端点 r 右边的分开,左端点之前的分开,然后对中间这段进行操作即可。

  1. void reverse( int l,int r )
  2. {
  3. int t1,t2,t3,t4;
  4. split( rt,r,t1,t2 ); split( t1,l-1,t3,t4 );
  5. tr[t4].mark^=1; rt=merge( merge( t3,t4 ),t2 );
  6. }

1. 基本操作

就是给原来的结构体加了翻转的 tag。

  1. struct FHQTreap
  2. {
  3. int l,r,val,rnd,siz,mark;
  4. }tr[N];

2. Pushdown

类似线段树的标记下传。

  1. void pushdown( int x )
  2. {
  3. if ( x && tr[x].mark )
  4. {
  5. tr[x].mark=0; swap( tr[x].l,tr[x].r );
  6. if ( tr[x].l ) tr[tr[x].l].mark^=1;
  7. if ( tr[x].r ) tr[tr[x].r].mark^=1;
  8. }
  9. }

3. Merge

合并操作中增加pushdown.

  1. int merge( int x,int y )
  2. {
  3. if ( !x || !y ) return x+y;
  4. pushdown( x ); pushdown( y );
  5. if ( tr[x].rnd<tr[y].rnd )
  6. { //make y -> x's right child
  7. tr[x].r=merge( tr[x].r,y ); update( x ); return x;
  8. }
  9. else
  10. { //make x -> y's left child
  11. tr[y].l=merge( x,tr[y].l ); update( y ); return y;
  12. }
  13. }

4. Split

由于是要翻转区间,所以一定要按照子树大小分割了。

记得下传。

  1. void split( int now,int k,int &x,int &y )
  2. {
  3. if ( !now ) { x=y=0; return; }
  4. pushdown( now );
  5. if ( k<=tr[tr[now].l].siz ) y=now,split( tr[now].l,k,x,tr[now].l );
  6. else x=now,split( tr[now].r,k-tr[tr[now].l].siz-1,tr[now].r,y );
  7. update( now );
  8. }

5. Build

新增的建树函数。总体结构类似线段树建树。

  1. int build( int l,int r )
  2. {
  3. if ( l>r ) return 0;
  4. int mid=(l+r)>>1,val=mid;
  5. int now=new_node( val );
  6. tr[now].l=build( l,mid-1 ); tr[now].r=build( mid+1,r );
  7. update( now );
  8. return now;
  9. }

6. 输出

这个是针对这道题的……就是中序遍历输出。

  1. void dfs( int x )
  2. {
  3. if ( !x ) return;
  4. pushdown( x );
  5. dfs( tr[x].l );
  6. printf( "%d ",tr[x].val );
  7. dfs( tr[x].r );
  8. }

FHQ简要笔记的更多相关文章

  1. Linux device tree 简要笔记

    第一.DTS简介     在嵌入式设备上,可能有不同的主板---它们之间差异表现在主板资源不尽相同,比如I2C.SPI.GPIO等接口定义有差别,或者是Timer不同,等等.于是这就产生了BSP的一个 ...

  2. 斯坦福大学CS231n简要笔记和课后作业

    笔记目录: 1. CS231n--图像分类(KNN实现) 2. 待更新... 3. 4.

  3. CSS权威指南之css声明,伪类,文本处理--(简要笔记一)

    1.css层叠的含义 后面的会覆盖前面的样式 2.每个元素生成一个框,也称盒.   3.替换元素和非替换元素. img如果不指定src的外部路径,该元素就没有意义.他由文档本身之外的一个图像文件来替换 ...

  4. 【转】CentOS上部署PPTP和L2TP over IPSec简要笔记

    PPTP部署 安装 PPTP 需要 MPPE 和较高版本的 ppp ( > 2.4.3 ) 支持,不过 CentOS 5.0/RHEL 5 的 2.6.18 内核已经集成了 MPPE 和高版本的 ...

  5. Java Gson 简要笔记

    Gson是Google开发的Java比较好用的 Json工具. 使用挺简单,假设有个类: class Runner { int attr; String name; public Runner(int ...

  6. sublime简要笔记

    选中单词 [1]选中当前单词 ctrl+d [2]跳过当前单词 ctrl+k ctrl+d [3]选中相同的所有单词 alt+f3 [4]多行游标 按住shift,然后按住鼠标右键向下拖动 行操作 [ ...

  7. [Objective-C 面试简要笔记]

    Obj-C: 1.消息机制 [shape draw]  向该对象发送消息,该对象查找并运行此函数 差不多就是shape.draw(); 2.中缀语法 [textThing setStringValue ...

  8. Asp.net MVC 单元测试 简要笔记

    首先要啰嗦几句. 单元测试是TDD的重要实践方法,也是代码质量的一种保证手段.在项目的工程化开发中,研发人员应该尽量保证书写Unit Test,即使不使用TDD. (VS中,我们可以直接使用微软提供的 ...

  9. 机器学习简要笔记(五)——Logistic Regression(逻辑回归)

    1.Logistic回归的本质 逻辑回归是假设数据服从伯努利分布,通过极大似然函数的方法,运用梯度上升/下降法来求解参数,从而实现数据的二分类. 1.1.逻辑回归的基本假设 ①伯努利分布:以抛硬币为例 ...

随机推荐

  1. KepServer与S7-1200PLC之间的OPC通信配置

    对于学习上位机开发,有一种通信方式是必须要了解的,那就是OPC是OLE for Process Control的简称,然而随着技术的不断发展,人们开始对它有了新的定义,比如Open Platform ...

  2. 一键SSH连接 = SSH密钥登陆 + WindowsTerminal

    本文记录如何利用SSH密钥登录和WindowsTerminal/FluentTerminal实现一键SSH连接 目录 一.在本地生成SSH密钥对 二.在远程主机安装公钥 三.在远程主机打开密钥登陆功能 ...

  3. python脚本打包成rpm软件包

    前言 软件最终都会有交付的形式,有的是用tar包,有个是以目录,有的是封成一个文件包,从大多数使用场景来说,直接打包成软件包的方式是最简单,也是最不容易出错的,路径可以在包里面写死了 实践 关于打包的 ...

  4. 丢了ceph.mon.keying解决办法

    在linux操作系统下,可能因为一些很小的误操作,都会造成非常重要的文件的丢失,而文件的备份并不是每时每刻都会注意到,一般是等到文件丢失了才会去想办法,这里讲下ceph.mon.keyring丢失的解 ...

  5. ubuntu安装软件自动交互

    在ubuntu下安装软件过程中可能会出现需要你输入密码或者其他的一些交互类的操作,这样在脚本安装的时候就可能出现阻断,这个在ubuntu里面已经考虑到了这个情况,以前我在安装这个的时候,通过的是脚本传 ...

  6. 网页中审查元素(按F12)与查看网页源代码的区别

    问题 在验证目标系统是含有XSS漏洞,查看源代码,看不到插入的跨站脚本代码. 原理 所谓查看源代码,就是别人服务器发送到浏览器的原封不动的代码. 审查元素时,你看到那些,在源代码中找不到的代码,是在浏 ...

  7. Camtasia中对录制视频进行编辑——音效

    市场上有很多的视频处理软件,形形色色的软件往往会使人眼花缭乱,而对于那些短视频的制作者来说,拥有一款好的视频处理软件会让自己的视频收获更多的点赞.那么今天我便给大家推荐一款同时具有录屏和编辑视频功能的 ...

  8. PDF文档工具:pdfFactory快照功能详解

    pdfFactory的快照功能,是通过一种类似截图的方式,将文档中的内容,如标题.图片.段落.文字等进行剪切的功能.剪切后的内容会转化为文本框的形式,我们可以对其进行加边框.旋转等编辑处理,但不能对其 ...

  9. 【???】今天上午的考试题——区间dp和字符串/线性筛的综合应用

    T3还没有打出来,就先放两道. ---------------------------------------------------------- T1:密码破译 温温手下的情报部门截获了一封加密信 ...

  10. 2019-2020 ICPC Asia Hong Kong Regional Contest J. Junior Mathematician 题解(数位dp)

    题目链接 题目大意 要你在[l,r]中找到有多少个数满足\(x\equiv f(x)(mod\; m)\) \(f(x)=\sum_{i=1}^{k-1} \sum_{j=i+1}^{k}d(x,i) ...