并不对劲的fhq treap
听说很对劲的太刀流不止会splay一种平衡树,并不对劲的片手流为了反驳他,并与之针锋相对,决定学学高端操作。
很对劲的太刀流->
据说splay常数极大,但是由于只知道splay一种平衡树能对序列进行操作,或者进行分裂合并,还不能不写它。
那么常数略小的treap能否对序列操作或者分裂合并呢?想必是能的。
这就是fhq大神的可分裂与合并的treap(据说国家队人手一个自创算法?那必须的不然ctsc论文答辩怎么过)。
它并没有比treap多出什么,反而省了好多事。核心操作只有split和merge。假设每个点有两个数key和w,平衡树维护的是key的性质,小根堆维护的是w的性质。
split(A,k):将A子树中排名不超过k的分一堆,排名超过k的分一堆。要返回两个值,分别是这两个平衡树的根。因为可能有多次祖父、父、子不共线的情况。
void spl(int u,int &l,int &r,int k)
{//称1st-kth为"前一部分",剩下的为"后一部分"
if(!k) l=0,r=u;//排名为0,显然所有数都在后一部分
else if(k==siz[u]) l=u,r=0;//排名与子树大小相等,显然所有数都在前一部分
else if(k<=siz[ls]) r=u,spl(ls,l,ls,k),up(u);//排名小于左子树大小说明右子树全是后一部分,那么还需要划分的就是前一部分
else l=u,spl(rs,rs,r,k-siz[ls]-1), up(u);//和上一行相反
}//会发现它们都是对称的
merge(A,B):类似可并堆。将A与B合并时(假设B中所有key都比A中所有key大),将w更大的合并到w更小的子树上。在这里为了满足二叉搜索树的性质,B合并到A上时要并道A的右儿子(key值都比A点大),A合并到B上时则反之。
void mrg(int &u,int l,int r)
{// l中所有数都小于r中所有数
if(!l || !r)u=l+r;//类似可并堆,有一边是空的就可以直接接过去了
else if(w[l]<w[r])u=l,mrg(rs,rs,r),up(u);//这是小根堆,所以是让w更小的当根
else u=r,mrg(ls,l,ls),up(u);//可并堆是和尽量和更小的儿子合并,但是还要满足平衡树的性质,所以l只能和r的左儿子,r只能和l的右儿子合并
}
其它操作都可以用这两个拼起来,比如insert就是在要插入的位置split,再将插入的数分别与左、右两边合并;delete就是在要删除的位置前后split,再将左右两边merge,中间就不要了。
但是取k数排名(rank)和第k大的数(kth)和splay不大一样。
rank:splay中,这个操作很方便,直接把k转到根就行了。而fhq treap中,不知道排名无法split,只能从上往下找了。
int rank(int u, int k)
{//求出的是小于k的数共有多少个 ,和splay里没什么区别
if(!u) return 0;
if(key[u]>=k) return rank(ls,k);//要注意可能会有一些点的权值相等,如果它们的权值等于k就不大妙了
return rank(rs,k)+siz[ls]+1;//这个函数求严格小于k的数有多少个的,所以比k大的数和等于k的数的处理方式是相同的
}
kth:splay中,取第k大数比较麻烦,因为只能将某个值的数转到根。但是fhq treap中就比较方便,将排名不超过k的split出来后,在split出排名不超过k-1的。剩下的就是排名为k的。
inline int kth(int k)
{
int x,y,z,ans;
spl(rt,x,y,k),spl(x,z,x,k-1),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;//split之后记得还原
}
普通平衡树的muti-set听上去很烦,但是fhq treap可以有多个相同的点。
这样听上去前驱后继都会变得难求。想必是可以通过巧妙使用split解决的。
prefix:看注释吧
inline int pre(int k)
{
int x,y,z,ans,rk=rank(rt,k);//第一次split后,x中的是所有小于k的;第二次split刚好分出所有小于x的中最大的
spl(rt,x,y,rk),spl(x,z,x,rk-1),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;
}
suffix:看注释吧
inline int suc(int k)
{
int x,y,z,ans,rk=rank(rt,k+1);//第一次split后,x中是所有小于k+1(小于等于k)的和一个大于等于k+1的;
spl(rt,x,y,rk+1),spl(x,z,x,rk),ans=key[x],mrg(x,z,x),mrg(rt,x,y);//第二次split刚好把那个最大的分出来
return ans;//前驱和后继挺对称的
}
令人不大愉快的是,其实无旋treap的常数和splay差不多。
令人愉快的是,由于没有旋转操作,它是可以可持久化的(不知道比替罪羊树高到哪里去了),而且不用记录父亲。
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<stack>
#include<vector>
#define rep(i,x,y) for(register int i=(x);i<=(y);i++)
#define dwn(i,x,y) for(register int i=(x);i>=(y);i--)
#define ls son[u][0]
#define rs son[u][1]
#define maxn 100010
#define inf 2147483647
#define s0 siz[0]=0
using namespace std;
int n,m,x,y,z,rt,cnt,tmp,opt;
int w[maxn],key[maxn],siz[maxn],son[maxn][];
inline int read()
{
int x=,f=;
char ch=getchar();
while(isdigit(ch)== && ch!='-')ch=getchar();
if(ch=='-')f=-,ch=getchar();
while(isdigit(ch))x=x*+ch-'',ch=getchar();
return x*f;
}
inline void write(int x)
{
int f=;char ch[];
if(!x){puts("");return;}
if(x<){putchar('-');x=-x;}
while(x)ch[++f]=x%+'',x/=;
while(f)putchar(ch[f--]);
putchar('\n');
}
inline void res(int &u, int k){w[u=++cnt]=rand()<<|rand(),key[u]=k,siz[u]=;}
inline void up(int u) {s0,siz[u]=siz[ls]+siz[rs]+,s0;}
void mrg(int &u,int l,int r)
{// l中所有数都小于r中所有数
if(!l || !r)u=l+r;//类似可并堆,有一边是空的就可以直接接过去了
else if(w[l]<w[r])u=l,mrg(rs,rs,r),up(u);//这是小根堆,所以是让w更小的当根
else u=r,mrg(ls,l,ls),up(u);//可并堆是和尽量和更小的儿子合并,但是还要满足平衡树的性质,所以l只能和r的左儿子,r只能和l的右儿子合并
}
void spl(int u,int &l,int &r,int k)
{//称1st-kth为"前一部分",剩下的为"后一部分"
if(!k) l=,r=u;//排名为0,显然所有数都在后一部分
else if(k==siz[u]) l=u,r=;//排名与子树大小相等,显然所有数都在前一部分
else if(k<=siz[ls]) r=u,spl(ls,l,ls,k),up(u);//排名小于左子树大小说明右子树全是后一部分,那么还需要划分的就是前一部分
else l=u,spl(rs,rs,r,k-siz[ls]-), up(u);//和上一行相反
}//会发现它们都是对称的
int rank(int u, int k)
{//求出的是小于k的数共有多少个 ,和splay里没什么区别
if(!u) return ;
if(key[u]>=k) return rank(ls,k);//要注意可能会有一些点的权值相等,如果它们的权值等于k就不大妙了
return rank(rs,k)+siz[ls]+;//这个函数求严格小于k的数有多少个的,所以比k大的数和等于k的数的处理方式是相同的
}
inline void insert(int k)
{
int x,y,rk=rank(rt,k);//x中的数是严格小于k的,y中的数是大于等于k的
spl(rt,x,y,rk),res(tmp,k),mrg(x,x,tmp),mrg(rt,x,y);
}
inline void del(int k)
{
int x,y,z,rk=rank(rt,k)+;//x中只有一个k,其余都是严格小于k的
spl(rt,x,y,rk),spl(x,x,z,rk-),mrg(rt,x,y);
}
inline int kth(int k)
{
int x,y,z,ans;
spl(rt,x,y,k),spl(x,z,x,k-),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;//split之后记得还原
}
inline int pre(int k)
{
int x,y,z,ans,rk=rank(rt,k);//第一次split后,x中的是所有小于k的;第二次split刚好分出所有小于x的中最大的
spl(rt,x,y,rk),spl(x,z,x,rk-),ans=key[x],mrg(x,z,x),mrg(rt,x,y);
return ans;
}
inline int suc(int k)
{
int x,y,z,ans,rk=rank(rt,k+);//第一次split后,x中是所有小于k+1(小于等于k)的和一个大于等于k+1的;
spl(rt,x,y,rk+),spl(x,z,x,rk),ans=key[x],mrg(x,z,x),mrg(rt,x,y);//第二次split刚好把那个最大的分出来
return ans;//前驱和后继挺对称的
}
int main()
{
srand();//据说这样能让人获得永生
n=read();w[]=key[]=inf;
rep(i,,n)
{
opt=read(),x=read();
if(opt==)insert(x);
if(opt==)del(x);
if(opt==)write(rank(rt, x)+);
if(opt==)write(kth(x));
if(opt==)write(pre(x));
if(opt==)write(suc(x));
}
}
其实有一种神奇的数据结构能够在O(1)的时间内完成所有操作,只不过不支持查询操作。它叫【我知道但我不告诉你】。
并不觉得盲目膜时提到国家集训队是对劲的,理同【弓这么用,威力堪比大剑三蓄】。
推荐一波电教G。
并不对劲的fhq treap的更多相关文章
- fhq treap最终模板
新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...
- NOI 2002 营业额统计 (splay or fhq treap)
Description 营业额统计 Tiger最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况. Tiger拿出了公司的账本,账本上记录了公司成立以来每 ...
- 【POJ2761】【fhq treap】A Simple Problem with Integers
Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...
- 【fhq Treap】bzoj1500(听说此题多码上几遍就能不惧任何平衡树题)
1500: [NOI2005]维修数列 Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 15112 Solved: 4996[Submit][Statu ...
- 「FHQ Treap」学习笔记
话说天下大事,就像fhq treap —— 分久必合,合久必分 简单讲一讲.非旋treap主要依靠分裂和合并来实现操作.(递归,不维护fa不维护cnt) 合并的前提是两棵树的权值满足一边的最大的比另一 ...
- FHQ Treap摘要
原理 以随机数维护平衡,使树高期望为logn级别 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 因此可持久化 先介绍变量 ; int n; struct Node { int v ...
- FHQ Treap小结(神级数据结构!)
首先说一下, 这个东西可以搞一切bst,treap,splay所能搞的东西 pre 今天心血来潮, 想搞一搞平衡树, 先百度了一下平衡树,发现正宗的平衡树写法应该是在二叉查找树的基础上加什么左左左右右 ...
- 在平衡树的海洋中畅游(四)——FHQ Treap
Preface 关于那些比较基础的平衡树我想我之前已经介绍的已经挺多了. 但是像Treap,Splay这样的旋转平衡树码亮太大,而像替罪羊树这样的重量平衡树却没有什么实际意义. 然而类似于SBT,AV ...
- 浅谈fhq treap
一.简介 fhq treap 与一般的treap主要有3点不同 1.不用旋转 2.以merge和split为核心操作,通过它们的组合实现平衡树的所有操作 3.可以可持久化 二.核心操作 代码中val表 ...
随机推荐
- laravel groupby分组问题。
laravel 5.7使用groupBy分组查询时会提示一个错误,但是sql可以执行. 因为:mysql从5.7以后,默认开启了严格模式. 解决方法:将/config/database.php 中:关 ...
- bzoj 2463 [中山市选2009]谁能赢呢? 博弈
[中山市选2009]谁能赢呢? Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 3014 Solved: 2165[Submit][Status][D ...
- dispatching(bzoj 2008)
Description 在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级. ...
- mysql 常用管理命令
常见的管理mysql命令 (1)用于选择在MySQL工作区指定的数据库(选择数据库): USE Databasename; (2)列出了MySQL数据库管理系统中的所有可访问的数据库: SHOW DA ...
- POJ 1704 Georgia and Bob【博弈】
题目链接: http://poj.org/problem?id=1704 题意: 给定棋子及其在格子上的坐标,两个人轮流选择一个棋子向左移动,每次至少移动一格,但是不可以碰到其他棋子.无路可走的时候视 ...
- 学习日常笔记<day12>jsp基础
1.Jsp基础 1.1Jsp引入 Servlet的作用:用java语言开发动态资源的技术 Jsp的作用:用java语言(+html语言)开发动态资源的技术 jsp就是servlet 1.2Jsp的特点 ...
- Spring MVC中 log4j日志文件配置相对路径
log4j和web.xml配置webAppRootKey 的问题 1 在web.xml配置 <context-param> <param-name>webAppRootKey ...
- SD/MMC的Commands和Responses的总结
SD总线通信是基于指令和数据比特流,起始位開始和停止位结束. SD总线通信有三个元素:1.Command:由host发送到卡设备.使用CMD线发送. 2.Response:从card端发送到host端 ...
- IOS开发 AFN和ASI
做项目有一段时间了,项目过程中处理网络请求难免的,而对于选择第三方来处理网络请求肯定是个明智的选择! AFNetworking和ASIHTTPRequest 这两个第三方该如何选择 我 ...
- Jackson说明
Jackson说明 package com.stono.sboot2_chp4_jackson.controller; import com.fasterxml.jackson.annotation. ...