[学习笔记]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,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
随机推荐
- mysql数据库 root密码重置
问题 忘记了MySQL的密码,网上搜索的杂七杂八,汇总一下. mysql版本是windows的mysql 5.7 步骤 1.以管理员身份打开cmd,切换到MySQL的bin目录 默认的话,一般是在C: ...
- [转]50 Tips for Working with Unity (Best Practices)
About these tips These tips are not all applicable to every project. They are based on my experience ...
- discuz修改附件出售用其他积分,与帖子不一样
现实中我遇到了这种情况,一个资源可以用两种积分购买,于是我决定用售卖贴和出售附件的方式,附件内容与贴内隐藏内容是一样的,但目前discuz的出售主题和附件使用的是同一种积分,有了此修改 1.首先是显示 ...
- OpenLDAP搭建部署
安装环境: linu系统: centos7.2版本 OenLDAP:/openldap-2.4.44 下载地址:ftp://ftp.openldap.org/pub/OpenLDAP/ope ...
- 【转】: 塞尔达组在GDC2017演讲的文字翻译:创新的勇气
大家好,我是藤林秀麿,以导演的身份参与<荒野之息>的制作,感谢大家的出席.我曾经作为设计者和导演制作了诸多塞尔达游戏(大地与时空之章.缩小帽.四支剑.幻影沙漏.天空之剑),回首望去,我已经 ...
- Qt 编程指南
Qt 编程指南 持续关注一本正在编写的Qt编程指南,期待作者早日完成创作.
- 微信小程序-----自定义验证码实现
这一段时间做小程序项目,使用的是mpvue的框架,需要自己实现验证码输入,模拟input的光标,上一个框输入后后一个框自动获取焦点,删除时从后往前依次删除.下图是整体效果: <template& ...
- RESTful源码笔记之RESTful Framework的基本组件
快速实例 Quickstart 序列化 创建一个序列化类 简单使用 开发我们的Web API的第一件事是为我们的Web API提供一种将代码片段实例序列化和反序列化为诸如json之类的表示形式的方式. ...
- 面对30页左右的运放数据手册datasheet,你需要知道如何看懂
1.输入失调电压(Input Offset Voltage) VOS 若将运放的两个输入端接地,理想运放输出为零,但实际运放输出不为零.此时,用输出电压除以增益得到的等效输入电压称为输入失调电压 ...
- 最全的NB-IoT芯片厂商、模组厂商信息
NB-IoT作为LPWAN(低功耗广域网)的新兴技术,因为具有低功耗.低成本.广覆盖.海量节点等优势,并且在授权频段可以与2G.3G无缝连接而被运营商所青睐且接受.特别是到了2017年,据统计全球有5 ...