[学习笔记]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,这个神仙(范浩强大佬),发明了这个神仙的数据结构 ...
随机推荐
- loadrunner12安装教程
全套五个文件: 独立安装包,插件包,LR安装包,语言包,版本说明书 loadrunner 12安装教程 1.首先下载Loadrunner12安装包.下载下来将会有四个安装包. HP_LoadRunne ...
- LearnPython - Zip格式文件的解压缩
import zipfile import os def unzip(zip_name, target_dir): files = zipfile.ZipFile(zip_name) for zip_ ...
- android点击事件的四种方式
android点击事件的四种方式 第一种方式:创建内部类实现点击事件 代码如下: package com.example.dail; import android.text.TextUtils; im ...
- 1.12Linux下软件安装(学习过程)
实验介绍 介绍 Ubuntu 下软件安装的几种方式,及 apt,dpkg 工具的使用. 一.Linux 上的软件安装 通常 Linux 上的软件安装主要有三种方式: 在线安装 从磁盘安装deb软件包 ...
- mvc4 找到多个与名为“xx”的控制器匹配的类型
asp.net mvc4 添加分区出现错误 找到多个与名为“home”的控制器匹配的类型 会出现如下错误”找到多个与名为“home”的控制器匹配的类型“ 在RouteConfig文件中添加命名空间可解 ...
- Maya脚本——重命名物体的名称
该脚本用于将图1中的命名变更为图2中的,把maya中使用相同名称的物体都重命名为不同的名称. 重命名的规则是:组名_原名称_序号 查阅了maya的官方手册:http://download.autode ...
- lintcode-223-回文链表
223-回文链表 设计一种方式检查一个链表是否为回文链表. 样例 1->2->1 就是一个回文链表. 挑战 O(n)的时间和O(1)的额外空间. 标签 链表 思路 找到链表中点后,翻转链表 ...
- scala程序运行的几种方式
HelloWorld简单实例 object HelloWorld{ def main(args:Array[String]){ println("HelloWorld") } } ...
- 复利计算程序单元测试(C语言)
对我们和复利计算程序,写单元测试. 有哪些场景? 期待的返回值 写测试程序. 运行测试. 我的复利计算程序是用C语言写的,不懂使用C语言的测试工具,所以用C语言的运行结果来反映测试结果. 测试模块(场 ...
- CentOS安装crontab及使用方法(转)
CentOS安装crontab及使用方法(转) 安装crontab:[root@CentOS ~]# yum install vixie-cron[root@CentOS ~]# yum ins ...