非旋 treap 结构体数组版(无指针)详解,有图有真相

非旋  $treap$ (FHQ treap)的简单入门

前置技能

建议在掌握普通 treap 以及 左偏堆(也就是可并堆)食用本blog

原理

以随机数维护平衡,使树高期望为logn级别, FHQ 不依靠旋转,只有两个核心操作merge(合并)和split(拆分)

所谓随机数维护平衡就是给每个节点一个随机值 key (下文中没有加随机的就代表是真实权值),

然后整棵树中 key 值要满足小(大)根堆的性质(也就是heap),

同时也要满足平衡树(tree)的性质(也就是每个节点左子树内节点真实权值小于它,右子树相反)

然后这个玩意儿就有了一个草率的名字:treap (tree 和 heap 的结合体)

结构体变量介绍

 1 int Rand() {  //伪随机函数,能让代码稍微变快
 2     static int seed=703;
 3     return seed=int(seed*48271LL%(~0u>>1));
 4 }
 5 struct Node {
 6     int val,key,siz,ch[2];
 7 // val 真实权值,key 随机权值,siz 子树大小 , ch 左右子节点
 8     void clear() {  //清空操作
 9         ch[0]=ch[1]=siz=val=key=0;
10     }
11 } t[M];
12 int update(int now){ //更新操作
13     t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1;
14 }

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 ,合并结束。

代码实现

 1 int merge(int u,int v) { // 此时 u 中节点权值均小于 v 中节点权值
 2     if(!u || !v) return u|v;  //某节点为空,直接返回另一节点
 3     if(t[u].key<t[v].key) { //以此满足 heap 性质
 4         t[u].ch[1]=merge(t[u].ch[1],v); // u 右子节点与 v 合并, 以满足平衡树性质
 5         update(u); return u;
 6     } else {
 7         t[v].ch[0]=merge(u,t[v].ch[0]); // u 与 v 左子节点合并, 以满足平衡树性质
 8         update(v); return v;
 9     }
10 }

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

1 void split_val(int now,int k,int& x,int& y) {
2     if(!now) return (void)(x=y=0); //节点为空, return
3     if(t[now].val<=k) //当前节点和它的左子树都满足进入左树的条件
4         x=now,split_val(t[now].ch[1],k,t[now].ch[1],y);
5     else //当前节点和它的右子树都满足进入右树的条件
6         y=now,split_val(t[now].ch[0],k,x,t[now].ch[0]);
7     update(now);
8 }

view code

2.按排名split

1 void split_k(int now,int k,int& x,int& y) { //与按权值 split 类似
2     if(!now) return (void)(x=y=0);
3     update(now);
4     if(t[t[now].ch[0]].siz<k)
5         x=now,split_k(t[now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
6     else
7         y=now,split_k(t[now].ch[0],k,x,t[now].ch[0]);
8     update(now);
9 }

view code

其他操作

FHQ treap 的核心操作只有 merge 和 split 两个,其他操作都是基于这两个操作实现的。

插入

插入权值为 x 的节点时,先新建一个节点,再以 x 为界按权值 split 整棵树为a,b,再按顺序 merge a,x,b。

代码实现

1 void ins(int x) {
2     int u,a,b;
3     t[u=++cnt].key=Rand();
4     t[u].val=x,t[u].siz=1;
5     split_val(root,x,a,b);
6     root=merge(merge(a,u),b);
7 }

view code

删除

要删除x,先将整棵树以 x-1 为界按权值split 成a和b,再将 b 以 1 为界 按排名split 成c和d,则 c 就是要删除的节点。最后按顺序merge a,b,d。

(当然,这是在要删除节点必定存在的情况下才能进行的操作,不存在的情况请自行脑补) 

代码实现

1 void del(int x) {
2     int a,b,c,d;
3     split_val(root,x-1,a,b);
4     split_k(b,1,c,d);
5     t[c].clear(),root=merge(a,d);
6 }

view code

查询 x 的排名

先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。

代码实现

1 int get_rank(int x) {
2     int a,b,c;
3     split_val(root,x-1,a,b);
4     c=t[a].siz+1;
5     root=merge(a,b);
6     return c;
7 }

view code

查询排名为 k 的值

先split出整棵树前k-1小节点,则右树最小节点即为所求节点,再次split 即可。

代码实现

1 int get_val(int& now,int x) {
2     int a,b,c,d,e;
3     split_k(now,x-1,a,b);
4     split_k(b,1,c,d);
5     e=t[c].val;
6     now=merge(a,merge(c,d));
7     return e;
8 }

view code

查x前驱

将整棵树以x-1按权值split,左树中最大节点即为所求节点,转入第x小值问题。

代码实现

1 int pre(int x) {
2     int a,b,c;
3     split_val(root,x-1,a,b);
4     c=get_val(a,t[a].siz);
5     root=merge(a,b);
6     return c;
7 }

view code

查x后继

将整棵树以x按权值split,右树中最小节点即为所求节点,转入第x小值问题。

代码实现

1 int sub(int x) {
2     int a,b,c;
3     split_val(root,x,a,b);
4     c=get_val(b,1);
5     root=merge(a,b);
6     return c;
7 }

view code

非旋 Treap的其他作用

非旋 trap 是支持区间操作的,具体其实就是你把原来的一棵树 split 成 3 棵树($1~l-1,l~r,r+1~n$),然后 我们对中间那棵树进行操作即可,具体代码不附上了

例题

洛谷P3369【模板】普通平衡树

代码

  1 //by Judge
  2 #include<iostream>
  3 #include<cstdio>
  4 using namespace std;
  5 const int M=1e5+111;
  6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
  7 char buf[1<<21],*p1=buf,*p2=buf;
  8 inline int read(){
  9     #define num ch-‘0‘
 10     char ch;bool flag=0;int res;
 11     while(!isdigit(ch=getc()))
 12     (ch==‘-‘)&&(flag=true);
 13     for(res=num;isdigit(ch=getc());res=res*10+num);
 14     (flag)&&(res=-res);
 15     #undef num
 16     return res;
 17 }
 18 char sr[1<<21],z[20];int C=-1,Z;
 19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
 20 inline void print(int x){
 21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
 22     while(z[++Z]=x%10+48,x/=10);
 23     while(sr[++C]=z[Z],--Z);sr[++C]=‘\n‘;
 24 }
 25 int n,cnt,root;
 26 int Rand() {
 27     static int seed=703;
 28     return seed=int(seed*48271LL%(~0u>>1));
 29 }
 30 struct Node {
 31     int val,key,siz,ch[2];
 32     void clear() {
 33         ch[0]=ch[1]=siz=val=key=0;
 34     }
 35 } t[M];
 36 int update(int now){
 37     t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1;
 38 }
 39 int merge(int u,int v) {
 40     if(!u || !v) return u|v;
 41     if(t[u].key<t[v].key) {
 42         t[u].ch[1]=merge(t[u].ch[1],v);
 43         update(u); return u;
 44     } else {
 45         t[v].ch[0]=merge(u,t[v].ch[0]);
 46         update(v); return v;
 47     }
 48 }
 49 void split_val(int now,int k,int& x,int& y) {
 50     if(!now) return (void)(x=y=0);
 51     if(t[now].val<=k)
 52         x=now,split_val(t[now].ch[1],k,t[now].ch[1],y);
 53     else
 54         y=now,split_val(t[now].ch[0],k,x,t[now].ch[0]);
 55     update(now);
 56 }
 57 void split_k(int now,int k,int& x,int& y) {
 58     if(!now) return (void)(x=y=0);
 59     update(now);
 60     if(t[t[now].ch[0]].siz<k)
 61         x=now,split_k(t[now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
 62     else
 63         y=now,split_k(t[now].ch[0],k,x,t[now].ch[0]);
 64     update(now);
 65 }
 66 void ins(int x) {
 67     int u,a,b;
 68     t[u=++cnt].key=Rand();
 69     t[u].val=x,t[u].siz=1;
 70     split_val(root,x,a,b);
 71     root=merge(merge(a,u),b);
 72 }
 73 void del(int x) {
 74     int a,b,c,d;
 75     split_val(root,x-1,a,b);
 76     split_k(b,1,c,d);
 77     t[c].clear(),root=merge(a,d);
 78 }
 79 int get_rank(int x) {
 80     int a,b,c;
 81     split_val(root,x-1,a,b);
 82     c=t[a].siz+1;
 83     root=merge(a,b);
 84     return c;
 85 }
 86 int get_val(int& now,int x) {
 87     int a,b,c,d,e;
 88     split_k(now,x-1,a,b);
 89     split_k(b,1,c,d);
 90     e=t[c].val;
 91     now=merge(a,merge(c,d));
 92     return e;
 93 }
 94 int pre(int x) {
 95     int a,b,c;
 96     split_val(root,x-1,a,b);
 97     c=get_val(a,t[a].siz);
 98     root=merge(a,b);
 99     return c;
100 }
101 int sub(int x) {
102     int a,b,c;
103     split_val(root,x,a,b);
104     c=get_val(b,1);
105     root=merge(a,b);
106     return c;
107 }
108 int main() {
109     n=read(); int opt,x;
110     while(n--){
111         opt=read(),x=read();
112         switch(opt){
113             case 1: ins(x); break;
114             case 2: del(x); break;
115             case 3: print(get_rank(x)); break;
116             case 4: print(get_val(root,x)); break;
117             case 5: print(pre(x)); break;
118             case 6: print(sub(x)); break;
119         }
120     } Ot(); return 0;
121 }

view code

然后这是压过行了的:

 1 //by Judge
 2 #include<iostream>
 3 #include<cstdio>
 4 using namespace std;
 5 const int M=1e5+111;
 6 #define getc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
 7 char buf[1<<21],*p1=buf,*p2=buf;
 8 inline int read(){
 9     #define num ch-‘0‘
10     char ch;bool flag=0;int res;
11     while(!isdigit(ch=getc()))
12     (ch==‘-‘)&&(flag=true);
13     for(res=num;isdigit(ch=getc());res=res*10+num);
14     (flag)&&(res=-res);
15     #undef num
16     return res;
17 }
18 char sr[1<<21],z[20];int C=-1,Z;
19 inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
20 inline void print(int x){
21     if(C>1<<20)Ot();if(x<0)sr[++C]=45,x=-x;
22     while(z[++Z]=x%10+48,x/=10);
23     while(sr[++C]=z[Z],--Z);sr[++C]=‘\n‘;
24 }
25 int n,cnt,root;
26 struct Node {
27     int val,key,siz,ch[2];
28     void clear() { ch[0]=ch[1]=siz=val=key=0; }
29 } t[M];
30 int Rand() { static int seed=703; return seed=int(seed*48271LL%(~0u>>1)); }
31 int update(int now){ t[now].siz=t[t[now].ch[0]].siz+t[t[now].ch[1]].siz+1; }
32 int merge(int u,int v) {
33     if(!u || !v) return u|v;
34     if(t[u].key<t[v].key) { t[u].ch[1]=merge(t[u].ch[1],v),update(u); return u; }
35     else { t[v].ch[0]=merge(u,t[v].ch[0]),update(v); return v; }
36 }
37 void split_val(int now,int k,int& x,int& y) {
38     if(!now) return (void)(x=y=0);
39     if(t[now].val<=k) split_val(t[x=now].ch[1],k,t[now].ch[1],y);
40     else split_val(t[y=now].ch[0],k,x,t[now].ch[0]);
41     update(now);
42 }
43 void split_k(int now,int k,int& x,int& y) {
44     if(!now) return (void)(x=y=0);
45     if(t[t[now].ch[0]].siz>=k) split_k(t[y=now].ch[0],k,x,t[now].ch[0]);
46     else split_k(t[x=now].ch[1],k-t[t[now].ch[0]].siz-1,t[now].ch[1],y);
47     update(now);
48 }
49 void ins(int x) { int u,a,b; t[u=++cnt].key=Rand(),t[u].val=x,t[u].siz=1,split_val(root,x,a,b),root=merge(merge(a,u),b); }
50 void del(int x) { int a,b,c,d; split_val(root,x-1,a,b),split_k(b,1,c,d),t[c].clear(),root=merge(a,d); }
51 int get_rank(int x) { int a,b,c; split_val(root,x-1,a,b),c=t[a].siz+1,root=merge(a,b); return c; }
52 int get_val(int& now,int x) { int a,b,c,d,e; split_k(now,x-1,a,b),split_k(b,1,c,d),e=t[c].val,now=merge(a,merge(c,d)); return e; }
53 int pre(int x) { int a,b,c; split_val(root,x-1,a,b),c=get_val(a,t[a].siz),root=merge(a,b); return c; }
54 int sub(int x) { int a,b,c; split_val(root,x,a,b),c=get_val(b,1),root=merge(a,b); return c; }
55 int main() {
56     n=read(); int opt,x;
57     while(n--){
58         opt=read(),x=read();
59         switch(opt){
60             case 1: ins(x); break;
61             case 2: del(x); break;
62             case 3: print(get_rank(x)); break;
63             case 4: print(get_val(root,x)); break;
64             case 5: print(pre(x)); break;
65             case 6: print(sub(x)); break;
66         }
67     } Ot(); return 0;
68 }

view code

最后感谢 axjcy 大佬的 blog

原文地址:https://www.cnblogs.com/Judge/p/9506980.html

时间: 2024-10-28 14:53:27

非旋 treap 结构体数组版(无指针)详解,有图有真相的相关文章

结构体定义 typedef struct 用法详解和用法小结

typedef是类型定义的意思.typedef struct 是为了使用这个结构体方便.具体区别在于:若struct node {}这样来定义结构体的话.在申请node 的变量时,需要这样写,struct node n;若用typedef,可以这样写,typedef struct node{}NODE; .在申请变量时就可以这样写,NODE n;区别就在于使用时,是否可以省去struct这个关键字. 第三篇:struct和typedef struct 分三块来讲述:1 首先:在C中定义一个结构体

C语言 - 结构体(struct)的位字段(:) 详解

结构体(struct)的位字段(:) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26722511 结构体(struct)可以使用位字段(:), 节省空间, 如以下代码, 结构体a中的, 第一个变量x占用1个字符, y占用2个字符, z占用33个字符(越界); 但是sizeof()会自动补齐, 如x+y一共占用4个字节, z占用8个字节, 所以结构体占用12个字节; 当使用加法运算时, 会初始化为0; 代码: /* *

沉迷数据结构1(treap&amp;非旋treap)

Achen大佬说不要沉迷数据结构否则智商会降低的. 从省选考完后就开始学treap,首先是自己yy了一个打了两百多行,然后debug了2个月还是3个月记不清了. 最后弃疗,去找了网上别人的代码抄了一遍. noip考完后补常规的一段时间,羡慕Achen能20分钟打出一个treap模板,于是自己也开始走上打板子的不归路. 到了后来可以10分钟左右打出一个结构体版的treap,看了Achen的数组版treap,觉得自己结构体版的太不优秀啦,于是就换成数组版的. 然后现在有几周没有碰过treap,感觉又

C#调用C/C++动态库 封送结构体,结构体数组

因为公司一直都是做C++开发的,因客户需要要提供C#版本接口,研究了一下C#,发现其强大简洁, 在跨语言调用方面封装的很彻底,提供了强大的API与之交互.这点比JNA方便多了. Java与C#都只能调用C格式导出动态库,因为C数据类型比较单一,容易映射. 两者都是在本地端提供一套与之映射的C#/java描述接口,通过底层处理这种映射关系达到调用的目的. 一. 结构体的传递 Cpp代码   #define JNAAPI extern "C" __declspec(dllexport) /

绝对好文C#调用C++DLL传递结构体数组的终极解决方案

C#调用C++DLL传递结构体数组的终极解决方案 时间 2013-09-17 18:40:56 CSDN博客相似文章 (0) 原文  http://blog.csdn.net/xxdddail/article/details/11781003 在项目开发时,要调用C++封装的DLL,普通的类型C#上一般都对应,只要用DllImport传入从DLL中引入函数就可以了.但是当传递的是结构体.结构体数组或者结构体指针的时候,就会发现C#上没有类型可以对应.这时怎么办,第一反应是C#也定义结构体,然后当

C#引用c++DLL结构体数组注意事项(数据发送与接收时)

本文转载自:http://blog.csdn.net/lhs198541/article/details/7593045 最近做的项目,需要在C# 中调用C++ 写的DLL,因为C# 默认的编码方式是Unicode,而调用的DLL规定只处理UTF8编码格式的字符串,DLL中的输入参数类型char*被我Marshal成byte[],输出参数类型char**被我Marshal成了string(C++和C#之间的类型转换请参阅相关资料),于是我就经历了无数次用于接收时的string-->string(

C#调用C++ 平台调用P/Invoke 结构体--输入输出参数、返回值、返出值、结构体数组作为参数【五】

[1]结构体作为输入输出参数 C++代码: typedef struct _testStru1 { int iVal; char cVal; __int64 llVal; }testStru1; EXPORTDLL_API void Struct_Change( testStru1 *pStru ) { if (NULL == pStru) { return; } pStru->iVal = 1; pStru->cVal = 'a'; pStru->llVal = 2; wprintf(

C的日记-结构体变量和结构体数组

[结构体] 定义结构体的两方式    <1>    struct student{};        struct student a={10001,"云中",'M',"北京"};    <2>    struct student{        }a={10001,"云中",'M',"北京"};定义结构体数组a换成a[],struct student stu[3]={{..},{..},{..}};

【C语言】用结构体数组完成:有5个学生(包括学号,姓名,成绩),要求按照成绩高低输出学生信息

//用结构体数组完成:有5个学生(包括学号,姓名,成绩),要求按照成绩高低输出学生信息 #include <stdio.h> struct Stu { int num; char name[20]; int score; }; int main() { int i,j; struct Stu student[5]={{317,"han",89},{318,"hu",50},{319,"kang",84},{320,"li&q