非旋 treap 结构体数组版(无指针)详解,有图有真相
非旋 $treap$ (FHQ treap)的简单入门
前置技能
建议在掌握普通 treap 以及 左偏堆(也就是可并堆)食用本blog
原理
以随机数维护平衡,使树高期望为logn级别, FHQ 不依靠旋转,只有两个核心操作merge(合并)和split(拆分)
所谓随机数维护平衡就是给每个节点一个随机值 key (下文中没有加随机的就代表是真实权值),
然后整棵树中 key 值要满足小(大)根堆的性质(也就是heap),
同时也要满足平衡树(tree)的性质(也就是每个节点左子树内节点真实权值小于它,右子树相反)
然后这个玩意儿就有了一个草率的名字:treap (tree 和 heap 的结合体)
结构体变量介绍
int Rand() { //伪随机函数,能让代码稍微变快
static int seed=;
return seed=int(seed*48271LL%(~0u>>));
}
struct Node {
int val,key,siz,ch[];
// val 真实权值,key 随机权值,siz 子树大小 , ch 左右子节点
void clear() { //清空操作
ch[]=ch[]=siz=val=key=;
}
} t[M];
int update(int now){ //更新操作
t[now].siz=t[t[now].ch[]].siz+t[t[now].ch[]].siz+;
}
veiw code
核心操作
merge操作
模型实现
假设有两颗子树x,y,且 x 的所有节点的值都小于 y 的所有节点的值,随机权值 key 都以小根堆的形式存储。
此时要合并 x , y 。我们先比较它们的根的随机权值,发现1<3,因为要满足小根堆性质,于是 x 的左子树全部不变,让它的右子树继续和 y 合并。
这时我们发现,随机权值 key 5>3,所以 y 接到 rot 的下方,成为 rot 的右儿子,y的右子树全部不变,让y的左子树继续和x合并(以满足平衡树的性质)。
由于5>4,所以y和y的右子树作为rot的左儿子,y的左子树继续和x合并。
5<7,所以接入x和它的左子树作为rot的左儿子。
至此,我们发现 x 为 0 ,所以直接返回 y ,合并结束。
代码实现
int merge(int u,int v) { // 此时 u 中节点权值均小于 v 中节点权值
if(!u || !v) return u|v; //某节点为空,直接返回另一节点
if(t[u].key<t[v].key) { //以此满足 heap 性质
t[u].ch[]=merge(t[u].ch[],v); // u 右子节点与 v 合并, 以满足平衡树性质
update(u); return u;
} else {
t[v].ch[]=merge(u,t[v].ch[]); // u 与 v 左子节点合并, 以满足平衡树性质
update(v); return v;
}
}
view code
split操作
split有两种拆分方式:
1. 按权值大小拆分
2. 按排名大小拆分。
模型实现
1.按权值split
首先得有个基准值 a ,即权值小于等于 a 的节点全部进入左树(下图中会将此类节点染红),大于a的节点全部进入右树(下图中会将此类节点染蓝)。这里以a=25为例。
首先,发现rot的权值=15<25,由平衡树的性质可知,rot的左子树所有节点权值一定小于25,所以rot和它的的左子树全部进入左树,继续拆分rot的右子树。
32>25,所以 rot 和它的右子树全部进入右树,继续拆分 rot 的左子树。
29>25,同上。
24<25,所以拆分右子树。
27>25,所以拆分左子树。
发现此时rot为0,所以拆分完毕,返回。
2.按排名split
就是把前 k 个节点拆入左树,其它节点拆入右树。这里以k=5为例。
rot的左子树的siz+1=3<5,所以rot和它的左子树进入左树,其他节点拆分5-3=2个节点进入左树。
4+1>2,所以rot和右子树进入右树,其它节点继续拆分出2个节点进入左树。
3+1>2,同上。
1+1=2,所以rot和左子树进入左树,其它节点继续拆分2-2=0个节点进入左树。
1+0>0,所以rot和右子树进入右树,其它节点继续拆分0个节点进入左树。
rot为0,拆分结束。
代码实现
1.按权值split
void split_val(int now,int k,int& x,int& y) {
if(!now) return (void)(x=y=); //节点为空, return
if(t[now].val<=k) //当前节点和它的左子树都满足进入左树的条件
x=now,split_val(t[now].ch[],k,t[now].ch[],y);
else //当前节点和它的右子树都满足进入右树的条件
y=now,split_val(t[now].ch[],k,x,t[now].ch[]);
update(now);
}
view code
2.按排名split
void split_k(int now,int k,int& x,int& y) { //与按权值 split 类似
if(!now) return (void)(x=y=);
update(now);
if(t[t[now].ch[]].siz<k)
x=now,split_k(t[now].ch[],k-t[t[now].ch[]].siz-,t[now].ch[],y);
else
y=now,split_k(t[now].ch[],k,x,t[now].ch[]);
update(now);
}
view code
其他操作
FHQ treap 的核心操作只有 merge 和 split 两个,其他操作都是基于这两个操作实现的。
插入
插入权值为 x 的节点时,先新建一个节点,再以 x 为界按权值 split 整棵树为a,b,再按顺序 merge a,x,b。
代码实现
void ins(int x) {
int u,a,b;
t[u=++cnt].key=Rand();
t[u].val=x,t[u].siz=;
split_val(root,x,a,b);
root=merge(merge(a,u),b);
}
view code
删除
要删除x,先将整棵树以 x-1 为界按权值split 成a和b,再将 b 以 1 为界 按排名split 成c和d,则 c 就是要删除的节点。最后按顺序merge a,b,d。
(当然,这是在要删除节点必定存在的情况下才能进行的操作,不存在的情况请自行脑补)
代码实现
void del(int x) {
int a,b,c,d;
split_val(root,x-,a,b);
split_k(b,,c,d);
t[c].clear(),root=merge(a,d);
}
view code
查询 x 的排名
先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。
代码实现
int get_rank(int x) {
int a,b,c;
split_val(root,x-,a,b);
c=t[a].siz+;
root=merge(a,b);
return c;
}
view code
查询排名为 k 的值
先split出整棵树前k-1小节点,则右树最小节点即为所求节点,再次split 即可。
代码实现
int get_val(int& now,int x) {
int a,b,c,d,e;
split_k(now,x-,a,b);
split_k(b,,c,d);
e=t[c].val;
now=merge(a,merge(c,d));
return e;
}
view code
查x前驱
将整棵树以x-1按权值split,左树中最大节点即为所求节点,转入第x小值问题。
代码实现
int pre(int x) {
int a,b,c;
split_val(root,x-,a,b);
c=get_val(a,t[a].siz);
root=merge(a,b);
return c;
}
view code
查x后继
将整棵树以x按权值split,右树中最小节点即为所求节点,转入第x小值问题。
代码实现
int sub(int x) {
int a,b,c;
split_val(root,x,a,b);
c=get_val(b,);
root=merge(a,b);
return c;
}
view code
非旋 Treap的其他作用
非旋 trap 是支持区间操作的,具体其实就是你把原来的一棵树 split 成 3 棵树($1~l-1,l~r,r+1~n$),然后 我们对中间那棵树进行操作即可,具体代码不附上了
例题
代码
//by Judge
#include<iostream>
#include<cstdio>
using namespace std;
const int M=1e5+;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while(!isdigit(ch=getc()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getc());res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
int n,cnt,root;
int Rand() {
static int seed=;
return seed=int(seed*48271LL%(~0u>>));
}
struct Node {
int val,key,siz,ch[];
void clear() {
ch[]=ch[]=siz=val=key=;
}
} t[M];
int update(int now){
t[now].siz=t[t[now].ch[]].siz+t[t[now].ch[]].siz+;
}
int merge(int u,int v) {
if(!u || !v) return u|v;
if(t[u].key<t[v].key) {
t[u].ch[]=merge(t[u].ch[],v);
update(u); return u;
} else {
t[v].ch[]=merge(u,t[v].ch[]);
update(v); return v;
}
}
void split_val(int now,int k,int& x,int& y) {
if(!now) return (void)(x=y=);
if(t[now].val<=k)
x=now,split_val(t[now].ch[],k,t[now].ch[],y);
else
y=now,split_val(t[now].ch[],k,x,t[now].ch[]);
update(now);
}
void split_k(int now,int k,int& x,int& y) {
if(!now) return (void)(x=y=);
update(now);
if(t[t[now].ch[]].siz<k)
x=now,split_k(t[now].ch[],k-t[t[now].ch[]].siz-,t[now].ch[],y);
else
y=now,split_k(t[now].ch[],k,x,t[now].ch[]);
update(now);
}
void ins(int x) {
int u,a,b;
t[u=++cnt].key=Rand();
t[u].val=x,t[u].siz=;
split_val(root,x,a,b);
root=merge(merge(a,u),b);
}
void del(int x) {
int a,b,c,d;
split_val(root,x-,a,b);
split_k(b,,c,d);
t[c].clear(),root=merge(a,d);
}
int get_rank(int x) {
int a,b,c;
split_val(root,x-,a,b);
c=t[a].siz+;
root=merge(a,b);
return c;
}
int get_val(int& now,int x) {
int a,b,c,d,e;
split_k(now,x-,a,b);
split_k(b,,c,d);
e=t[c].val;
now=merge(a,merge(c,d));
return e;
}
int pre(int x) {
int a,b,c;
split_val(root,x-,a,b);
c=get_val(a,t[a].siz);
root=merge(a,b);
return c;
}
int sub(int x) {
int a,b,c;
split_val(root,x,a,b);
c=get_val(b,);
root=merge(a,b);
return c;
}
int main() {
n=read(); int opt,x;
while(n--){
opt=read(),x=read();
switch(opt){
case : ins(x); break;
case : del(x); break;
case : print(get_rank(x)); break;
case : print(get_val(root,x)); break;
case : print(pre(x)); break;
case : print(sub(x)); break;
}
} Ot(); return ;
}
view code
然后这是压过行了的:
//by Judge
#include<iostream>
#include<cstdio>
using namespace std;
const int M=1e5+;
#define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while(!isdigit(ch=getc()))
(ch=='-')&&(flag=true);
for(res=num;isdigit(ch=getc());res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
int n,cnt,root;
struct Node {
int val,key,siz,ch[];
void clear() { ch[]=ch[]=siz=val=key=; }
} t[M];
int Rand() { static int seed=; return seed=int(seed*48271LL%(~0u>>)); }
int update(int now){ t[now].siz=t[t[now].ch[]].siz+t[t[now].ch[]].siz+; }
int merge(int u,int v) {
if(!u || !v) return u|v;
if(t[u].key<t[v].key) { t[u].ch[]=merge(t[u].ch[],v),update(u); return u; }
else { t[v].ch[]=merge(u,t[v].ch[]),update(v); return v; }
}
void split_val(int now,int k,int& x,int& y) {
if(!now) return (void)(x=y=);
if(t[now].val<=k) split_val(t[x=now].ch[],k,t[now].ch[],y);
else split_val(t[y=now].ch[],k,x,t[now].ch[]);
update(now);
}
void split_k(int now,int k,int& x,int& y) {
if(!now) return (void)(x=y=);
if(t[t[now].ch[]].siz>=k) split_k(t[y=now].ch[],k,x,t[now].ch[]);
else split_k(t[x=now].ch[],k-t[t[now].ch[]].siz-,t[now].ch[],y);
update(now);
}
void ins(int x) { int u,a,b; t[u=++cnt].key=Rand(),t[u].val=x,t[u].siz=,split_val(root,x,a,b),root=merge(merge(a,u),b); }
void del(int x) { int a,b,c,d; split_val(root,x-,a,b),split_k(b,,c,d),t[c].clear(),root=merge(a,d); }
int get_rank(int x) { int a,b,c; split_val(root,x-,a,b),c=t[a].siz+,root=merge(a,b); return c; }
int get_val(int& now,int x) { int a,b,c,d,e; split_k(now,x-,a,b),split_k(b,,c,d),e=t[c].val,now=merge(a,merge(c,d)); return e; }
int pre(int x) { int a,b,c; split_val(root,x-,a,b),c=get_val(a,t[a].siz),root=merge(a,b); return c; }
int sub(int x) { int a,b,c; split_val(root,x,a,b),c=get_val(b,),root=merge(a,b); return c; }
int main() {
n=read(); int opt,x;
while(n--){
opt=read(),x=read();
switch(opt){
case : ins(x); break;
case : del(x); break;
case : print(get_rank(x)); break;
case : print(get_val(root,x)); break;
case : print(pre(x)); break;
case : print(sub(x)); break;
}
} Ot(); return ;
}
view code
推荐题目
题目
洛谷 —— 列队
分析
首先我们分析一下这个列队的性质;
首先每次操作时,先让一个人 (a,b) 出队 。
然后右边的人跟上来,空缺的位置变成了 (a,m) (也就是在一棵平衡树中删除了一个节点)
最后其实也就是最后一列的跟到上面,空缺的位置变成 (n,m) ,然后出队的人到这个位置上
于是非常显然的,题目就是要你维护 n 棵平衡树,
但是看看数据范围:3e5 ... 于是发现这题不可做
那么我萌就要用到一个巧妙的思路—— 缩点与拆点 了。
什么意思? 你再看眼数据范围:3e5 ... 没毛病?
对啊,询问也是 3e5 啊... 所以说我们是不是维护了许多用都用不到的点呢?
于是我们把一大段区间的没用的点缩成一个点,让这个点记录区间左右信息就好了咯。
emmm...你说的没错,我们怎么知道哪些点该缩哪些点不该缩呢?
简单,我们点全都缩起来,要用的时候再拆出来不就好了?
然后还要注意的就是最后一列的特殊性,要单独维护(也就 3e5 个点嘛,开得下),
这点从图中应该也看得出来(何况这些点并不属于一个区间!)
于是愉快地上代码...
代码
//by Judge
#include<iostream>
#include<cstdio>
#define ll long long
#define int long long
using namespace std;
const int M=3e5+;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++) // 手动输入需要去掉这句话
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
ll n,m,q,cnt,root,rt[M];
struct Node { int key,ch[]; ll l,r,siz; } t[M<<];
inline int Rand() { static int seed=; return seed=(ll)(seed*48271LL%(~0u>>)); }
inline int update(int now){ t[now].siz=t[t[now].ch[]].siz+t[t[now].ch[]].siz+t[now].r-t[now].l+; }
inline int newnode(ll x,ll y) { int u=++cnt; t[u].l=x,t[u].r=y,t[u].siz=y-x+,t[u].key=Rand(); return u; }
int merge(int u,int v) {
if(!u || !v) return u|v;
if(t[u].key<t[v].key) { t[u].ch[]=merge(t[u].ch[],v),update(u); return u; }
else { t[v].ch[]=merge(u,t[v].ch[]),update(v); return v; }
}
void split_new(ll now,ll k){ //把一个节点拆成两个节点
if(k>=t[now].r-t[now].l+) return ; int nw=newnode(t[now].l+k,t[now].r);
t[now].r=t[now].l+k-,t[now].ch[]=merge(nw,t[now].ch[]),update(now);
}
void split(int now,int k,int& x,int& y) { //常规操作
if(!now) return (void)(x=y=);
if(t[t[now].ch[]].siz>=k) split(t[y=now].ch[],k,x,t[now].ch[]);
else{
split_new(now,k-t[t[now].ch[]].siz),k-=t[t[now].ch[]].siz,
split(t[x=now].ch[],k-(t[now].r-t[now].l+),t[now].ch[],y);
} update(now);
}
signed main(){
n=read(),m=read(),q=read();
for(ll i=;i<=n;++i)
rt[i]=newnode((i-)*m+,i*m-);
for(ll i=;i<=n;++i)
rt[n+]=merge(rt[n+],newnode(i*m,i*m));
while(q--){
ll a=read(),b=read(),x,y,z;
if(b^m){
ll xx,yy,zz; split(rt[a],b,x,y),
split(x,b-,x,z),print(t[z].l),
split(rt[n+],a,xx,yy),split(xx,a-,xx,zz);
rt[a]=merge(merge(x,y),zz),rt[n+]=merge(merge(xx,yy),z); //拆完合并
}
else{
split(rt[n+],a,x,y),split(x,a-,x,z);
print(t[z].l),rt[n+]=merge(merge(x,y),z);
}
} Ot(); return ;
}
最后感谢 axjcy 大佬的 blog
非旋 treap 结构体数组版(无指针)详解,有图有真相的更多相关文章
- 结构体定义 typedef struct 用法详解和用法小结
typedef是类型定义的意思.typedef struct 是为了使用这个结构体方便.具体区别在于:若struct node {}这样来定义结构体的话.在申请node 的变量时,需要这样写,stru ...
- MSG结构体和WndProc窗口过程详解
MSG结构体和WndProc窗口过程对于Windows编程非常重要,如果不了解它们,可以说就没有学会Windows编程. MSG结构体 MSG 结构体用来表示一条消息,各个字段的含义如下: typed ...
- matlab学习笔记12_2创建结构体数组,访问标量结构体,访问非标量结构体数组的属性,访问嵌套结构体中的数据,访问非标量结构体数组中多个元素的字段
一起来学matlab-matlab学习笔记12 12_2 结构体 创建结构体数组,访问标量结构体,访问非标量结构体数组的属性,访问嵌套结构体中的数据,访问非标量结构体数组中多个元素的字段 觉得有用的话 ...
- C#调用C/C++动态库 封送结构体,结构体数组
一. 结构体的传递 #define JNAAPI extern "C" __declspec(dllexport) // C方式导出函数 typedef struct { int ...
- C#调用C++DLL传递结构体数组的终极解决方案
在项目开发时,要调用C++封装的DLL,普通的类型C#上一般都对应,只要用DllImport传入从DLL中引入函数就可以了.但是当传递的是结构体.结构体数组或者结构体指针的时候,就会发现C#上没有类型 ...
- 绝对好文C#调用C++DLL传递结构体数组的终极解决方案
C#调用C++DLL传递结构体数组的终极解决方案 时间 2013-09-17 18:40:56 CSDN博客相似文章 (0) 原文 http://blog.csdn.net/xxdddail/art ...
- C#调用C/C++动态库 封送结构体,结构体数组
因为实验室图像处理的算法都是在OpenCV下写的,还有就是导航的算法也是用C++写的,然后界面部分要求在C#下写,所以不管是Socket通信,还是调用OpenCV的DLL模块,都设计到了C#和C++数 ...
- C#引用c++DLL结构体数组注意事项(数据发送与接收时)
本文转载自:http://blog.csdn.net/lhs198541/article/details/7593045 最近做的项目,需要在C# 中调用C++ 写的DLL,因为C# 默认的编码方式是 ...
- C语言利用结构体数组实现学生成绩管理系统
这篇文章主要为大家详细介绍了C语言利用结构体数组实现学生成绩管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 要求: 某班有最多不超过30人(具体人数由键盘输入) ...
随机推荐
- Vultr CentOS 7 安装 Docker
前言 最近在梳理公司的架构,想用 VPS 先做一些测试,然后就开始踩坑了!我用 Vultr 新买了个 VPS. 安装的 CentOS 版本: [root@dbn-seattle ~]# cat /et ...
- 领域驱动设计系列文章(2)——浅析VO、DTO、DO、PO的概念、区别和用处
本篇文章主要讨论一下我们经常会用到的一些对象:VO.DTO.DO和PO. 由于不同的项目和开发人员有不同的命名习惯,这里我首先对上述的概念进行一个简单描述,名字只是个标识,我们重点关注其概念: 概念: ...
- 几行c#代码,轻松搞定一个女大学生
几行c#代码,轻松搞定一个女大学生 的作业... 哈哈,标题党了哈,但是是真的,在外面敲代码,想赚点外快,接到了一个学生的期末考试,是一个天气预报的程序.程序并不难. 看到这个需求第一个想法就是只要找 ...
- codeforces#1132 F. Clear the String(神奇的区间dp)
题意:给出一个字符串S,|S|<=500.每次操作可以删除一段连续的相同字母的子串.问,最少操作多少次可以把这个字符串变成空串. 分析:刚开始的思路是,把连续的串给删除掉,然后再....贪心.完 ...
- Neutron :默认通过 dnsmasq 实现 DHCP 功能----Namespace
Neutron 提供 DHCP 服务的组件是 DHCP agent. DHCP agent 在网络节点运行上,默认通过 dnsmasq 实现 DHCP 功能. 配置 DHCP agent DHCP ...
- ES6相关
1.变量声明 let 和const 传统的 var 关键字声明变量,会存在变量提升.在ES6中,我们用 let 和 const 声明,let 声明变量,const 声明常量,let 和 const 都 ...
- python之装饰器初识
一.@abstractmethod 1.抽象类的作用:规范编程模式 多人开发.复杂的需求.后期的扩展 是一种用来帮助我们完成规范化的手段 2.如何定义抽象类 1,from abc import ABC ...
- kubernetes 报错汇总
一. pod的报错: 1. pod的容器无法启动报错: 报错信息: Normal SandboxChanged 4m9s (x12 over 5m18s) kubelet, k8sn1 Pod san ...
- 小程序运行报错:errMsg: "request:fail url not in domain list"
错误原因: 报错提示说请求的url不在域名列表里,应该是还没有配置服务器域名 解决方法: 可点击开发者工具右上角 详情-项目设置-不校验合法域名.web-view(业务域名).TLS 版本以及 HTT ...
- 牛客网 223C 区区区间间间(单调栈)
题目链接:区区区间间间 题意:给出长度为n的数字序列ai,定义区间(l,r)的价值为, 请你计算出. 题解:单调栈求ai左边和右边第一个比它小的位置,需要减去ai的个数为$(R_i-i+1)*(i-L ...