前言

原文写于 XJ 集训day2 2020.1.19.

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

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

普通平衡树模板

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

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

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

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

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

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

模板题

1. 基本操作

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

1. Split

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

首先是按照权值的写法:

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

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

最后不要忘记 update。

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

然后是按照排名的写法:

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

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

2. Merge

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

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

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

int merge( int x,int y )
{
if ( !x || !y ) return x+y;
if ( tr[x].rnd<tr[y].rnd )
{ //make y -> x's right child
tr[x].r=merge( tr[x].r,y ); update( x ); return x;
}
else
{ //make x -> y's left child
tr[y].l=merge( x,tr[y].l ); update( y ); return y;
}
}

3. Kth

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

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

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

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

这里采用 while 替代递归。

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

4. 基本查询

插入

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

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

删除

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

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

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

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

根据值求排名

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

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

Kth

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

求前驱

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

求后继

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

文艺平衡树

模板题

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

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

0. 【Important】Reverse

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

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

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

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

1. 基本操作

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

struct FHQTreap
{
int l,r,val,rnd,siz,mark;
}tr[N];

2. Pushdown

类似线段树的标记下传。

void pushdown( int x )
{
if ( x && tr[x].mark )
{
tr[x].mark=0; swap( tr[x].l,tr[x].r );
if ( tr[x].l ) tr[tr[x].l].mark^=1;
if ( tr[x].r ) tr[tr[x].r].mark^=1;
}
}

3. Merge

合并操作中增加pushdown.

int merge( int x,int y )
{
if ( !x || !y ) return x+y;
pushdown( x ); pushdown( y );
if ( tr[x].rnd<tr[y].rnd )
{ //make y -> x's right child
tr[x].r=merge( tr[x].r,y ); update( x ); return x;
}
else
{ //make x -> y's left child
tr[y].l=merge( x,tr[y].l ); update( y ); return y;
}
}

4. Split

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

记得下传。

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

5. Build

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

int build( int l,int r )
{
if ( l>r ) return 0;
int mid=(l+r)>>1,val=mid;
int now=new_node( val );
tr[now].l=build( l,mid-1 ); tr[now].r=build( mid+1,r );
update( now );
return now;
}

6. 输出

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

void dfs( int x )
{
if ( !x ) return;
pushdown( x );
dfs( tr[x].l );
printf( "%d ",tr[x].val );
dfs( tr[x].r );
}

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. http代理阅读2

    向上游服务器发送请求处理 static void ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t * ...

  2. nginx&http 第五章 https non-fd 读写检测

    EPOLL的LT/ET 模式下的读写 从一个非阻塞的socket上调用recv/send函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)从字面上看, ...

  3. tcp输入数据 慢速路径处理 && oob数据 接收 && 数据接收 tcp_data_queue

    大致的处理过程 TCP的接收流程:在tcp_v4_do_rcv中的相关处理(网卡收到报文触发)中,会首先通过tcp_check_urg设置tcp_sock的urg_data为TCP_URG_NOTYE ...

  4. Centos7下Jewel版本radosgw服务启动

    前言 本篇介绍了centos7下jewel版本的radosgw配置,这里的配置是指将服务能够正常起来,不涉及到S3的配置,以及其他的更多的配置,radosgw后面的gw就是gateway的意思,也就是 ...

  5. Python_faker (伪装者)创建假数据

    faker (伪装者)创建假数据 工作中,有时候我们需要伪造一些假数据,如何使用 Python 伪造这些看起来一点也不假的假数据呢? Python 有一个包叫 Faker,使用它可以轻易地伪造姓名.地 ...

  6. 基于 abp vNext 微服务开发的敏捷应用构建平台 - 框架分析

    总体架构     本平台从技术上采用ABP vNext和.NET Core编写的微服务架构.客户端层主要以现代浏览器为主,适配了PC端和移动端的访问,采用API和应用程序进行交互,同时提供第三方使用的 ...

  7. Java web 自动备份数据库和log4j日志

    利用监听自动备份 web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns: ...

  8. DDBNet:Anchor-free新训练方法,边粒度IoU计算以及更准确的正负样本 | ECCV 2020

    论文针对当前anchor-free目标检测算法的问题提出了DDBNet,该算法对预测框进行更准确地评估,包括正负样本以及IoU的判断.DDBNet的创新点主要在于box分解和重组模块(D&R) ...

  9. 万字长文!从底层开始带你了解并发编程,彻底帮你搞懂java锁!

    线程是否要锁住同步资源 锁住 悲观锁 不锁住 乐观锁 锁住同步资源失败 线程是否要阻塞 阻塞 不阻塞自旋锁,适应性自旋锁 多个线程竞争同步资源的流程细节有没有区别 不锁住资源,多个线程只有一个能修改资 ...

  10. Boom 3D的广播有哪些,有啥特色

    Boom 3D(Windows系统)不仅为用户提供了包括3D立体音效.古典音乐音效在内的多种音效增强功能,而且还为用户提供了广播功能.该广播功能不仅涵盖了国内广播节目,而且还涵盖了国际广播节目. 接下 ...