[学习笔记]FHQ-Treap及其可持久化
感觉范浩强真的巨
博主只刷了模板所以就讲基础
fhq-treap
又形象的称为非旋转treap
顾名思义
保留了treap的随机数堆的特点,并以此作为复杂度正确的条件
并且所有的实现不用旋转!
思路自然,结构直观,代码简洁,理解轻松。
虽然不能支持LCT(起码我不会)
但是相较于splay可以可持久化(splay理论上旋转会造成空间爆炸)
基本和splay平分秋色,甚至更胜一筹
核心操作只有两个:
merge:把两个树合成一个树
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].pri<t[y].pri){
t[x].ch[]=merge(t[x].ch[],y);
pushup(x);
return x;
}else{
t[y].ch[]=merge(x,t[y].ch[]);
pushup(y);
return y;
}
}
(merge最后只有一棵树,所以可以带返回值)
需要注意的是,merge的两棵树,默认x的所有权值都小于y!
split:把一个树分裂成两个树
按照权值k分裂
void split(int now,int k,int &x,int &y){
if(!now){
x=;y=;return;
}
if(t[now].val<=k){
x=now;
split(t[now].ch[],k,t[now].ch[],y);
}else{
y=now;
split(t[now].ch[],k,x,t[now].ch[]);
}
pushup(now);
}
按照sz分裂:(维护序列时候)
void split(int now,int k,int &x,int &y){
if(!now) {
x=;y=;return;
}
pushdown(now);
if(t[t[now].ch[]].sz+<=k){
k-=t[t[now].ch[]].sz+;
x=now;
split(t[now].ch[],k,t[now].ch[],y);
}else{
y=now;
split(t[now].ch[],k,x,t[now].ch[]);
}
pushup(now);
}
(由于split最后得到两棵树,所以只能用&)
模拟一下很直观的。
其他的操作直接看代码就可以理解:
fhq不用记录father的
#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
char ch;x=;bool fl=false;
while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
for(x=numb;isdigit(ch=getchar());x=x*+numb);
(fl==true)&&(x=-x);
}
namespace Miracle{
const int N=+;
const int inf=0x3f3f3f3f;
struct node{
int pri;
int ch[];
int val;
int sz;
}t[N];
int tot;
int rt;
int n;
int nc(int v){
t[++tot].val=v;t[tot].sz=;
t[tot].pri=rand();
return tot;
}
void pushup(int x){
t[x].sz=t[t[x].ch[]].sz+t[t[x].ch[]].sz+;
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].pri<t[y].pri){
t[x].ch[]=merge(t[x].ch[],y);
pushup(x);
return x;
}else{
t[y].ch[]=merge(x,t[y].ch[]);
pushup(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
if(!now){
x=;y=;return;
}
if(t[now].val<=k){
x=now;
split(t[now].ch[],k,t[now].ch[],y);
}else{
y=now;
split(t[now].ch[],k,x,t[now].ch[]);
}
pushup(now);
}
int kth(int now,int k){
int x=now;
while(){
if(t[t[x].ch[]].sz+==k) return x;
int tmp=t[t[x].ch[]].sz+;
if(tmp<k){
k-=tmp;x=t[x].ch[];
}else{
x=t[x].ch[];
}
}
}
int main(){
srand();
rd(n);
int op,x;
int le=,ri=;
while(n--){
rd(op);rd(x);
switch (op){
case :{
split(rt,x,le,ri);
rt=merge(merge(le,nc(x)),ri);
break;
}
case :{
int zz;
split(rt,x,le,zz);
split(le,x-,le,ri);
//le=t[le].ch[0];//warning!!! maybe wrong
ri=merge(t[ri].ch[],t[ri].ch[]);
rt=merge(merge(le,ri),zz);
break;
}
case :{
split(rt,x-,le,ri);
printf("%d\n",t[le].sz+);
rt=merge(le,ri);
break;
}
case :{
int tmp=kth(rt,x);
printf("%d\n",t[tmp].val);
break;
}
case :{
split(rt,x-,le,ri);
int tmp=kth(le,t[le].sz);
printf("%d\n",t[tmp].val);
rt=merge(le,ri);
break;
}
case :{
split(rt,x,le,ri);
int tmp=kth(ri,);
printf("%d\n",t[tmp].val);
rt=merge(le,ri);
break;
}
}
}
return ;
} }
signed main(){
Miracle::main();
return ;
} /*
Author: *Miracle*
Date: 2019/1/5 21:36:18
*/
值得一提的是
fhq可以有重复的点
在求第k大的时候直接二分不会出问题
其他时候可能会有问题。。
所以其他时候都要split和merge
左子树,右子树的值可能存在和自己相等的情况
但是由于merge的大小一定是x小于y,所以不会出现右子树有比自己小的,左子树有比自己大的。
所以还是可以直接找第k大的。(当然按照size分裂也可以)
大概是这样吧
区间操作?
分成三棵树[1,l-1],[l,r],[r+1,n]
对于中间的树打上标记或者查询询问
然后依次merge起来
值得一提的是,可以不用加入什么0,n+1两个节点,空树在fhq-treap中不会有任何影响
(splay就有点难受了,把0splay到根会出各种各样的问题。因为0作为哨兵,father,ch都是假的)
然后就可以做文艺平衡树了:
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
char ch;bool fl=false;
while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
for(x=numb;isdigit(ch=getchar());x=x*+numb);
(fl==true)&&(x=-x);
}
namespace Miracle{
const int N=+;
int n;
struct node{
int rev,ch[];
int sz;
int val;
int pri;
}t[N];
int tot;
int rt;
int nc(int c){
++tot;t[tot].val=c;t[tot].pri=rand();t[tot].sz=;
return tot;
}
void pushup(int x){
t[x].sz=t[t[x].ch[]].sz+t[t[x].ch[]].sz+;
}
void rev(int x){
swap(t[x].ch[],t[x].ch[]);
t[x].rev^=;
}
void pushdown(int x){
if(t[x].rev){
rev(t[x].ch[]);rev(t[x].ch[]);
t[x].rev=;
}
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].pri<t[y].pri){
pushdown(x);
t[x].ch[]=merge(t[x].ch[],y);
pushup(x);
return x;
}
else{
pushdown(y);
t[y].ch[]=merge(x,t[y].ch[]);
pushup(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
if(!now) {
x=;y=;return;
}
pushdown(now);
if(t[t[now].ch[]].sz+<=k){
k-=t[t[now].ch[]].sz+;
x=now;
split(t[now].ch[],k,t[now].ch[],y);
}else{
y=now;
split(t[now].ch[],k,x,t[now].ch[]);
}
pushup(now);
}
void op(int x){
pushdown(x);
if(t[x].ch[]) op(t[x].ch[]);
if(t[x].val>) printf("%d ",t[x].val);
if(t[x].ch[]) op(t[x].ch[]);
}
int main(){
srand();
rd(n);
rt=nc(-);
for(reg i=;i<=n;++i) rt=merge(rt,nc(i));
rt=merge(rt,nc(-));
int m;
rd(m);
int l,r;
int x,y,z;
while(m--){
rd(l);rd(r);
split(rt,l,x,z);
split(z,r-l+,z,y);
rev(z);
rt=merge(merge(x,z),y);
}
op(rt);
return ;
} }
signed main(){
Miracle::main();
return ;
} /*
Author: *Miracle*
Date: 2019/1/6 8:41:45
*/
最厉害的是,fhq可以可持久化!
由于出色的简单操作和结构的相对稳定性,使得一次操作不会产生太多的点
(其实splay均摊操作logn,旋转暴力建节点也是可以的吧,,但是常数和空间都大到飞起~)
总之完爆splay几条街
具体只用多几行:
split的时候,对于劈出来的这条链上每个点都建立一个新节点
merge的时候,合并出来的点也是新的节点
正确性的话,只要不会影响之前版本的查询,显然就没有问题
形成了二子多父的实际局面(和主席树也是一样的)
然后每个版本的根记录好即可。
垃圾回收还是有必要的
而且发现,merge总在split之后,split已经把新节点建好了。所以merge可以不建节点
注意第k大,保证有sz才去找,否则就RE辣。
代码:
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
char ch;bool fl=false;
while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
for(x=numb;isdigit(ch=getchar());x=x*+numb);
(fl==true)&&(x=-x);
}
namespace Miracle{
const int N=5e5+;
const int inf=;
struct node{
int val,sz;
int ch[],pri;
}t[N*];
int tot;
int rt[N];
int m;
int dp[N],top;
int nc(int v){
int r=top?dp[top--]:++tot;
t[r].val=v;t[r].sz=;
t[r].pri=rand();t[r].ch[]=t[r].ch[]=;
return r;
}
int copy(int x){
int r=top?dp[top--]:++tot;
t[r]=t[x];return r;
}
void pushup(int x){
t[x].sz=t[t[x].ch[]].sz+t[t[x].ch[]].sz+;
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].pri<t[y].pri){
//int now=copy(x);
t[x].ch[]=merge(t[x].ch[],y);
pushup(x);
return x;
}
else{
// int now=copy(y);
t[y].ch[]=merge(x,t[y].ch[]);
pushup(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
if(!now){
x=;y=;return;
}
if(t[now].val<=k){
x=copy(now);
split(t[now].ch[],k,t[x].ch[],y);
pushup(x);
}
else{
y=copy(now);
split(t[now].ch[],k,x,t[y].ch[]);
pushup(y);
}
}
int kth(int now,int k){
int x=now;
// cout<<" now "<<now<<" "<<t[now].sz<<" and "<<k<<endl;
if(t[now].sz==) {
// cout<<" ??? "<<endl;
return inf;
}
while(){
// cout<<x<<endl;
if(t[t[x].ch[]].sz+==k) return x;
int tmp=t[t[x].ch[]].sz+;
if(tmp<k) k-=tmp,x=t[x].ch[];
else x=t[x].ch[];
}
return ;
}
int main(){
srand();
int n;
rd(n);
int st,op,a;
int x,y,z;
for(reg i=;i<=n;++i){
rd(st);rd(op);rd(a);
if(op==){
split(rt[st],a,x,y);
//cout<<" after split "<<x<<" "<<y<<endl;
rt[i]=merge(merge(x,nc(a)),y);
//cout<<" rt "<<rt[i]<<" "<<t[rt[i]].sz<<" "<<t[rt[i]].val<<" :: "<<t[rt[i]].ch[0]<<" "<<t[rt[i]].ch[1]<<endl;
}else if(op==){
split(rt[st],a,x,y);
// cout<<" after split "<<x<<" "<<y<<endl;
if(t[x].sz==||t[kth(x,t[x].sz)].val!=a) {//no exist
// cout<<" no exist "<<endl;
rt[i]=merge(x,y);
continue;
}
// cout<<" after check "<<endl;
split(x,a-,x,z);
//cout<<" after split2 "<<endl;
z=merge(t[z].ch[],t[z].ch[]);
//cout<<" after dele "<<endl;
rt[i]=merge(merge(x,z),y);
//cout<<" after merge=end"<<endl;
}else if(op==){
split(rt[st],a-,x,y);
printf("%d\n",t[x].sz+);
rt[i]=merge(x,y);
}else if(op==){
rt[i]=rt[st];
printf("%d\n",t[kth(rt[i],a)].val);
}else if(op==){
split(rt[st],a-,x,y);
if(t[x].sz==){
printf("%d\n",-inf);
}else{
printf("%d\n",t[kth(x,t[x].sz)].val);
}
rt[i]=merge(x,y);
}else{
split(rt[st],a,x,y);
if(t[y].sz==){
printf("%d\n",inf);
}else{
printf("%d\n",t[kth(y,)].val);
}
rt[i]=merge(x,y);
}
}
return ;
} }
signed main(){
Miracle::main();
return ;
} /*
Author: *Miracle*
Date: 2019/1/6 11:11:11
*/
这个模板太朴素
我第一次用主席树水过去了,还有人用可持久化0/1trie
尝试真正平衡树才能做的:
区间翻转!序列插入一个数!序列删除一个数!
然后有了此题:
区间翻转的标记下放的时候
如果有儿子,就新建一个。没有这个儿子就不用建了。空节点还是不能随便建的。否则TLE爆炸
用上垃圾回收和merge不用建立节点节省空间
就可以过啦。
#include<bits/stdc++.h>
#define il inline
#define reg register int
#define numb (ch^'0')
using namespace std;
typedef long long ll;
il void rd(int &x){
char ch;bool fl=false;
while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
for(x=numb;isdigit(ch=getchar());x=x*+numb);
(fl==true)&&(x=-x);
}
namespace Miracle{
const int N=2e5+;
int n;
int rt[N];
struct node{
int pri,sz;
int ch[],rev;
ll sum,val;
}t[N*];
int tot;
int dp[N],top;
int nc(int v){
int r=top?dp[top--]:++tot;
t[r].val=v;t[r].sum=v;t[r].sz=;
t[r].pri=rand();t[r].ch[]=t[r].ch[]=;
t[r].rev=;
return r;
}
int cpy(int x){
int r=top?dp[top--]:++tot;
t[r]=t[x];return r;
}
void pushup(int x){
if(!x) return;
t[x].sz=t[t[x].ch[]].sz+t[t[x].ch[]].sz+;
t[x].sum=t[t[x].ch[]].sum+t[t[x].ch[]].sum+t[x].val;
}
void pushdown(int x){
if(!x) return;
if(t[x].rev){
if(t[x].ch[]&&t[x].ch[]){
int ls=cpy(t[x].ch[]),rs=cpy(t[x].ch[]);
t[x].ch[]=ls;
t[x].ch[]=rs;
t[ls].rev^=;t[rs].rev^=;
}else if(t[x].ch[]){
int ls=cpy(t[x].ch[]);
t[x].ch[]=ls;
t[x].ch[]=;t[ls].rev^=;
}else if(t[x].ch[]){
int rs=cpy(t[x].ch[]);
t[x].ch[]=rs;
t[x].ch[]=;t[rs].rev^=;
}
t[x].rev=;
pushup(x);
}
}
int merge(int x,int y){
if(!x||!y) return x+y;
if(t[x].pri<t[y].pri){
pushdown(x);
//int now=cpy(x);
t[x].ch[]=merge(t[x].ch[],y);
pushup(x);
return x;
}else{
pushdown(y);
//int now=cpy(y);
t[y].ch[]=merge(x,t[y].ch[]);
pushup(y);
return y;
}
}
void split(int now,int k,int &x,int &y){
//cout<<" spliting "<<now<<" k "<<k<<" :: "<<x<<" "<<y<<endl;
//cout<<" infor of now "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl;
if(t[now].sz==){
x=;y=;return;
}
pushdown(now);
//cout<<" after pushdown "<<t[now].sz<<" "<<t[now].rev<<" || "<<t[now].val<<" "<<t[t[now].ch[0]].val<<" "<<t[t[now].ch[1]].val<<" "<<t[t[t[now].ch[1]].ch[1]].val<<" || "<<tot<<endl;
if(t[t[now].ch[]].sz+<=k){
k-=t[t[now].ch[]].sz+;
x=cpy(now);
split(t[now].ch[],k,t[x].ch[],y);
pushup(x);
}else{
// cout<<" cpy y"<<endl;
y=cpy(now);
split(t[now].ch[],k,x,t[y].ch[]);
pushup(y);
}
}
int main(){
srand();
rd(n);
ll las=;
int st,op;
int p;ll a;
int l,r;
int x,y,z;
for(reg i=;i<=n;++i){
rd(st);rd(op);
if(op==){
scanf("%d%lld",&p,&a);
p^=las;a^=las;
// cout<<" insert "<<endl;
// cout<<" p a "<<p<<" "<<a<<endl;
split(rt[st],p,x,y);
rt[i]=merge(merge(x,nc(a)),y);
}else if(op==){
scanf("%d",&p);
p^=las;
split(rt[st],p,x,y);
split(x,p-,x,z);
dp[++top]=z;
rt[i]=merge(x,y);
}else if(op==){ scanf("%d%d",&l,&r);
l^=las;r^=las;
// cout<<" reverse "<<endl;
// cout<<" l r "<<l<<" "<<r<<endl;
split(rt[st],r,x,y);
split(x,l-,x,z);
z=cpy(z);
t[z].rev^=;
rt[i]=merge(merge(x,z),y);
}else {
scanf("%d%d",&l,&r);
l^=las;r^=las;
// cout<<" query "<<endl;
/// cout<<" l r "<<l<<" "<<r<<endl;
split(rt[st],r,x,y);
// cout<<" split 1 "<<endl;
split(x,l-,x,z);
// cout<<" split 2 "<<endl;
las=t[z].sum;
printf("%lld\n",las);
rt[i]=merge(merge(x,z),y);
}
}
return ;
} }
signed main(){
Miracle::main();
return ;
} /*
Author: *Miracle*
Date: 2019/1/6 12:20:00
*/
总结:
范浩强太巨啦
简单实用,你值得拥有。
[学习笔记]FHQ-Treap及其可持久化的更多相关文章
- 「学习笔记」Treap
「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...
- FHQ Treap及其可持久化与朝鲜树式重构
FHQ Treap,又称无旋treap,一种不基于旋转机制的平衡树,可支持所有有旋treap.splay等能支持的操作(只有在LCT中会比splay复杂度多一个log).最重要的是,它是OI中唯一一种 ...
- Redis学习笔记(八) RDB持久化
Redis是内存数据库,它将自己的数据库状态存储在内存里面,所以如果不想办法将存储在内存中的数据库状态保存到磁盘,那么服务器 进程一旦退出,服务器中的数据库状态也会消失不见. 为了解决这个问题,Red ...
- Redis学习笔记(九) AOF持久化
除了RDB持久化功能之外,Redis还提供了AOF持久化功能.与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的. 服务 ...
- RabbitMQ学习笔记(6)----RabbitMQ 持久化和非持久化
持久化:将交换机或队列数据保存到磁盘,服务器宕机或重启之后依然存在. 非持久化:将交换机或队列的数据保存到内存中,服务器宕机或重启之后数据将不存在. 在RabbitMQ中也提供了持久化和非持久化方式. ...
- [学习笔记] 平衡树——Treap
前置技能:平衡树前传:BST 终于学到我们喜闻乐见的平衡树啦! 所以我们这次讲的是平衡树中比较好写的\(Treap\). (以后会写splay的先埋个坑在这) 好了,进入正题. step 1 我们知道 ...
- fhq treap 学习笔记
序 今天心血来潮,来学习一下fhq treap(其实原因是本校有个OIer名叫fh,当然不是我) 简介 fhq treap 学名好像是"非旋转式treap及可持久化"...听上去怪 ...
- fhq treap最终模板
新学习了fhq treap,厉害了 先贴个神犇的版, from memphis /* Treap[Merge,Split] by Memphis */ #include<cstdio> # ...
- redis学习笔记(3)
redis学习笔记第三部分 --redis持久化介绍,事务,主从复制 三,redis的持久化 RDB(Redis DataBase)AOF(Append Only File) RDB:在指定的时间间隔 ...
- FHQ treap学习(复习)笔记
.....好吧....最后一篇学习笔记的flag它倒了..... 好吧,这篇笔记也鸽了好久好久了... 比赛前刷模板,才想着还是补个坑吧... FHQ,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
随机推荐
- Configure,Makefile.am, Makefile.in, Makefile文件
一 软件安装关于 makefile文件问题 如果拿到的工程文件中,没有Makefile文件,而只有configure.in和Makefile.am文件,我们是不能够直接进行编译的,必须根据config ...
- Spring Cloud Learning(一): 服务注册
官网https://projects.spring.io/spring-cloud/,spring cloud官网各组件版本为: Component Edgware.SR4 Finchley.SR1 ...
- SQL Server变量杂谈
学习SQL的过程有进步的话还是一件很美妙的事情的 在第一家公司虽然只呆了两年,但是感觉是我进步最快的两年.那时候工作和生活都挺充实的,每天都有一点点的收获和付出,其中最大的收获莫过于掌握一些核心技能. ...
- Qt creator 最常用的13个快捷键
alt +enter // 自动创建类的定义 F1 // 查看帮助,文档 F2 // 快速到变量声明 Shift + F2 // 函数的声明和定义之间快速切换 F4 // 在 cpp 和 h 文件切换 ...
- 【python 3.6】python读取json数据存入MySQL(一)
整体思路: 1,读取json文件 2,将数据格式化为dict,取出key,创建数据库表头 3,取出dict的value,组装成sql语句,循环执行 4,执行SQL语句 #python 3.6 # -* ...
- Cuteftp连接虚拟机Centos7
使用Centos7虚拟机时,想要从主机传一些文件到虚拟机,需要使用FTP传输,在主机上装上的CuteFTP的软件,对虚拟机进行配置. 1,首先,要保证虚拟机能够上网 一般装好虚拟机后,只要主机连了网, ...
- JAVA学习笔记--匿名内部类
匿名内部类,即没有名字的内部类. 我们在编写JAVA程序时,往往要创建很多类,类是可以被重复使用的.但有时,我们创建了一个类,却只需要使用该类一次,那么单独为其编写一个类就显得有些麻烦,这时可以使用匿 ...
- Requests库入门——应用实例-网络图片的爬取与保存(好看的小姐姐≧▽≦)
在B站学习这一节的时候,弹幕最为激烈,不管大家是出于什么目的都想体验一下网络爬虫爬取图片的魅力,毕竟之前的实例实话说都是一些没有太大作用的信息. 好了,直接上代码: import requests i ...
- .net web 应用程序C#
简介 开发环境:VS2015 ASP.NET:可以开发出几乎所有运行在Windows上的应用程序:.NET是一种架构,一种新的API:引入程序集代替DLL: ADO.NET:一组.NET组件提供对数据 ...
- flask验证登录学习过程(1)---准备
对应flask的接口开发,目前自己可以熟练的进行.但是深入到更基础的,从注册到验证登录的过程一直不是特别清楚. 趁着年度不是特别忙的时候,特意去学习加强一下.把这个过程记录在此处. 首先是规划一个项目 ...