FHQ Treap摘要
原理
以随机数维护平衡,使树高期望为logn级别
不依靠旋转,只有两个核心操作merge(合并)和split(拆分)
因此可持久化
先介绍变量
1 const int N=100005; 2 int n; 3 struct Node { 4 int val,key,siz; //权值,随机权值,子树大小 5 int son[2]; //左右儿子(0左1右) 6 void res() { //清空该节点(用于删除) 7 son[0]=son[1]=siz=val=key=0; 8 } 9 } tree[N]; 10 int ins; 11 int mem[N],inm; //内存回收池 12 int root;
核心操作
merge并返回合并后的根
假设有两颗子树x,y,且x的所有节点的值都小于y的所有节点的值,随机权值都以小根堆的形式存储。
此时要合并x和y。我们先比较它们的根的随机权值,发现1<3,则x的左子树全部不变,让右子树继续和y合并。
这时我们发现,5>3,所以y作为rot的右儿子,y的右子树全部不变,让y的左子树继续和x合并。
由于5>4,所以y和y的右子树作为rot的左儿子,y的左子树继续和x合并。
5<7,所以接入x和它的左子树作为rot的左儿子。
发现此时x为0,所以直接返回y,合并结束。
1 int merge(int x,int y) { //合并两棵树 2 if(!x||!y) return x+y; //若有一棵树为0则说明该树为空或已合并完成 3 if(tree[x].key<tree[y].key) { //若x的随机权值大于y的 4 tree[x].son[1]=merge(tree[x].son[1],y); //x的右子树和y合并,返回的根作为x的右子树 5 update(x); 6 return x; //返回x 7 } else { 8 tree[y].son[0]=merge(x,tree[y].son[0]); //否则y的左子树和x合并,返回的根作为y的左子树 9 update(y); 10 return y; //返回y 11 } 12 }
split拆分一棵树
split有两种拆分方式,按权值拆或按排名拆。
按权值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,所以拆分完毕,返回。
1 void split1(int now,int k,int &x,int &y) { //按权值拆分两颗子树(注意要用引用) 2 if(!now) { //子树为0,说明无需拆分或拆分完毕,返回 3 x=y=0; 4 return; 5 } 6 if(tree[now].val<=k) { //若权值小于等于k 7 x=now; 8 split1(tree[now].son[1],k,tree[now].son[1],y); //拆进左树并拆分右子树 9 } else { 10 y=now; 11 split1(tree[now].son[0],k,x,tree[now].son[0]); //否则拆进右树并拆分左子树 12 } 13 update(now); 14 }
按排名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 void split2(int now,int k,int &x,int &y) { //按权值拆分两颗子树(同样要用引用) 2 if(!now) { //子树为0,说明无需拆分或拆分完毕,返回 3 x=y=0; 4 return; 5 } 6 update(now); 7 if(k>tree[tree[now].son[0]].siz) { //若做子树大小+1小于等于k 8 x=now; 9 split2(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);//拆进左树并拆分右子树(注意右子树分配的名额要减少) 10 } else { 11 y=now; 12 split2(tree[now].son[0],k,x,tree[now].son[0]); //否则拆进右树并拆分左子树 13 } 14 update(now); 15 }
其他操作
会了merge和split,其他操作就是瞎搞。
插入
插入x,先新建节点,再以x为界按权值split整棵树为a,b,再按顺序merge a,x,b。
1 void insert(int x) { 2 int u=(inm?mem[inm--]:++ins); //新建节点 3 tree[u].key=rand(); 4 tree[u].val=x; 5 tree[u].siz=1; 6 int a,b; 7 split1(root,x,a,b); //split 8 root=merge(merge(a,u),b); //merge 9 }
删除
要删除x,先将整棵树以x按权值split成a和b,再将a以x-1按权值split成c和d,则d中节点权值全为x。在d中split出排名为1的节点e和其它节点f,则e为要删的点。最后merge c,f,b。
1 void delet(int x) { 2 int a,b,c,d,e,f; 3 split1(root,x,a,b); //split整棵树 4 split1(a,x-1,c,d); //将a split为c和d 5 split2(d,1,e,f); //将d split为e和f,则e为我们要删的节点 6 mem[++inm]=e; //回收 7 tree[e].res(); //重置 8 root=merge(merge(c,f),b); //merge 9 }
查询x的排名
先将整棵树以x-1按权值split成a和b,则a的siz+1即为x的排名。
1 int finrnk(int x) { 2 int a,b,c; 3 split1(root,x-1,a,b); //split整棵树 4 c=tree[a].siz+1; //a的大小就是小于x的数的个数 5 root=merge(a,b); //merge 6 return c; 7 }
查询第x小值
先split出整棵树前x-1小节点,则右树最小节点即为所求节点,再次split即可。
1 int finnum(int &rot,int x) { 2 int a,b,c,d,e; 3 split2(rot,x-1,a,b); //split这棵树 4 split2(b,1,c,d); //split出b中第1个节点 5 e=tree[c].val; //c即为第x小节点 6 rot=merge(a,merge(c,d)); //merge 7 return e; 8 }
查x前驱
将整棵树以x-1按权值split,左树中最大节点即为所求节点,转入第x小值问题。
1 int las(int x) { 2 int a,b,c; 3 split1(root,x-1,a,b); //split整棵树 4 c=finnum(a,tree[a].siz); //找左树最大值 5 root=merge(a,b); //merge 6 return c; 7 }
查x后继
将整棵树以x按权值split,右树中最小节点即为所求节点,转入第x小值问题。
1 int nex(int x) { 2 int a,b,c; 3 split1(root,x,a,b); //split整棵树 4 c=finnum(b,1); //找右树最小值 5 root=merge(a,b); //merge 6 return c; 7 }
时空复杂度
时间复杂度
merge、split:期望树高为logn,因此复杂度为期望O(logn)
插入、删除、查询:基于以上两种操作,复杂度期望O(logn)
常数比Treap大,但比splay小的多
空间复杂度
O(n)
例题
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define INF 0x7fffffff 4 #define ME 0x7f 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout) 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c)) 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c)) 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i]) 9 #define ll long long 10 #define MEM(a,b) memset(a,b,sizeof(a)) 11 #define maxn (100000+10) 12 int n; 13 struct Node{int val,key,siz;int son[2];void res(){son[0]=son[1]=siz=val=key=0;}}tree[maxn]; 14 int ins,mem[maxn],inm,root; 15 template<class T> 16 inline T read(T &n){ 17 n=0;int t=1;double x=10;char ch; 18 for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0'; 19 for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0'; 20 if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10; 21 return (n*=t); 22 }void update(int x){tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+1;}int merge(int x,int y){if(!x||!y) return x+y; 23 if(tree[x].key<tree[y].key){tree[x].son[1]=merge(tree[x].son[1],y);update(x);return x;} 24 else{tree[y].son[0]=merge(x,tree[y].son[0]);update(y);return y;} 25 }void split1(int now,int k,int &x,int &y){if(!now){x=y=0;return;} 26 if(tree[now].val<=k){x=now;split1(tree[now].son[1],k,tree[now].son[1],y);} 27 else{y=now;split1(tree[now].son[0],k,x,tree[now].son[0]);}update(now); 28 }void split2(int now,int k,int &x,int &y){if(!now){x=y=0;return;}update(now); 29 if(k>tree[tree[now].son[0]].siz){x=now; 30 split2(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);} 31 else{y=now;split2(tree[now].son[0],k,x,tree[now].son[0]);}update(now); 32 }void insert(int x){int u=(inm?mem[inm--]:++ins); 33 tree[u].key=rand();tree[u].val=x;tree[u].siz=1; 34 int a,b;split1(root,x,a,b);root=merge(merge(a,u),b); 35 }void delet(int x){int a,b,c,d,e,f; 36 split1(root,x,a,b);split1(a,x-1,c,d);split2(d,1,e,f); 37 mem[++inm]=e;tree[e].res();root=merge(merge(c,f),b); 38 }int finrnk(int x){int a,b,c;split1(root,x-1,a,b);c=tree[a].siz+1;root=merge(a,b);return c;} 39 int finnum(int &rot,int x){int a,b,c,d,e;split2(rot,x-1,a,b); 40 split2(b,1,c,d);e=tree[c].val;rot=merge(a,merge(c,d));return e; 41 }int las(int x){int a,b,c;split1(root,x-1,a,b);c=finnum(a,tree[a].siz);root=merge(a,b);return c;} 42 int nex(int x){int a,b,c;split1(root,x,a,b);c=finnum(b,1);root=merge(a,b);return c;} 43 int main(){ 44 read(n); 45 fui(i,1,n,1){ 46 int opt,x;read(opt);read(x); 47 switch(opt){ 48 case 1:insert(x);break; 49 case 2:delet(x);break; 50 case 3:cout<<finrnk(x)<<endl;break; 51 case 4:cout<<finnum(root,x)<<endl;break; 52 case 5:cout<<las(x)<<endl;break; 53 case 6:cout<<nex(x)<<endl;break; 54 } 55 } 56 return 0; 57 }
FHQ Treap的其他作用
最重要的一点是它可以代替区间操作!而且支持可持久化!!!
区间操作
将每个点按它们的下标作为关键字,其他的像普通FHQ Treap就行了。
区间翻转的话,每次merge和split都pushdown一下。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define INF 0x7fffffff 4 #define ME 0x7f 5 #define FO(s) freopen(s".in","r",stdin);freopen(s".out","w",stdout) 6 #define fui(i,a,b,c) for(int i=(a);i<=(b);i+=(c)) 7 #define fdi(i,a,b,c) for(int i=(a);i>=(b);i-=(c)) 8 #define fel(i,a) for(register int i=h[a];i;i=ne[i]) 9 #define ll long long 10 #define MEM(a,b) memset(a,b,sizeof(a)) 11 #define maxn (100000+10) 12 int n,m; 13 struct Node{ 14 int key,val; 15 int siz,son[2]; 16 char iz; 17 Node(){key=val=siz=son[0]=son[1]=iz=0;} 18 Node(int x,int y){key=x,val=y,siz=1,son[0]=son[1]=iz=0;} 19 }tree[maxn]; 20 int root; 21 int l,r; 22 int rnd(){static int seed=703;return seed=int(seed*48271LL%(~0u>>1));} 23 template<class T> 24 inline T read(T &n){ 25 n=0;int t=1;double x=10;char ch; 26 for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());(ch=='-')?t=-1:n=ch-'0'; 27 for(ch=getchar();isdigit(ch);ch=getchar()) n=n*10+ch-'0'; 28 if(ch=='.') for(ch=getchar();isdigit(ch);ch=getchar()) n+=(ch-'0')/x,x*=10; 29 return (n*=t); 30 } 31 void update(int x){tree[x].siz=tree[tree[x].son[0]].siz+tree[tree[x].son[1]].siz+1;} 32 void pushdown(int x){ 33 if(tree[x].iz){ 34 tree[x].iz=0;swap(tree[x].son[0],tree[x].son[1]); 35 tree[tree[x].son[0]].iz^=1;tree[tree[x].son[1]].iz^=1; 36 } 37 } 38 int merge(int x,int y){ 39 if(!x||!y) return x+y;pushdown(x);pushdown(y); 40 if(tree[x].key<tree[y].key){tree[x].son[1]=merge(tree[x].son[1],y);update(x);return x;} 41 else{tree[y].son[0]=merge(x,tree[y].son[0]);update(y);return y;} 42 } 43 void split(int now,int k,int &x,int &y){ 44 if(!now){x=y=0;return;}pushdown(now); 45 if(tree[tree[now].son[0]].siz>=k){y=now;split(tree[now].son[0],k,x,tree[now].son[0]);} 46 else{x=now;split(tree[now].son[1],k-tree[tree[now].son[0]].siz-1,tree[now].son[1],y);} 47 update(now); 48 } 49 void dfs(int now){ 50 pushdown(now); 51 if(tree[now].son[0]) dfs(tree[now].son[0]); 52 printf("%d ",tree[now].val); 53 if(tree[now].son[1]) dfs(tree[now].son[1]); 54 } 55 int main(){ 56 read(n);read(m); 57 fui(i,1,n,1){tree[i]=(Node){rnd(),i};root=merge(root,i);} 58 fui(i,1,m,1){ 59 read(l);read(r);int a,b,c; 60 split(root,r,a,c);split(a,l-1,a,b); 61 tree[b].iz^=1; 62 root=merge(merge(a,b),c); 63 } 64 dfs(root); 65 return 0; 66 }
可持久化
还没折腾出来。。。最近也没时间折腾了。。。来日再说吧。。。