zoukankan      html  css  js  c++  java
  • 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;
    var

    核心操作

    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 }
    merge

    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 }
    split1

    按排名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 }
    split2

    其他操作

    会了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 }
    insert

    删除

    要删除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 }
    delet

    查询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 }
    finrnk

    查询第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 }
    finnum

    查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 }
    las

    查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 }
    nex

    时空复杂度

    时间复杂度

    merge、split:期望树高为logn,因此复杂度为期望O(logn)

    插入、删除、查询:基于以上两种操作,复杂度期望O(logn)

    常数比Treap大,但比splay小的多

    空间复杂度

    O(n)

    例题

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

     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 }
    AC代码

    FHQ Treap的其他作用

    最重要的一点是它可以代替区间操作!而且支持可持久化!!!

    区间操作

    将每个点按它们的下标作为关键字,其他的像普通FHQ Treap就行了。

    区间翻转的话,每次merge和split都pushdown一下。

    洛谷【模板】文艺平衡树(Splay)

     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 }
    AC代码

    可持久化

    还没折腾出来。。。最近也没时间折腾了。。。来日再说吧。。。

    作者:A星际穿越
    我的博客写得这么烂,应该不会有人想转载的吧
    如果要转载的话,请在文章显眼处标明作者和出处谢谢
  • 相关阅读:
    没有一个计时器控制在VB6计时器功能
    检测系统范围内的鼠标事件
    c# Com
    tcpdump
    dd
    dumpe/dumpe2fs/e2fsck
    fdisk
    mkswap/swapon/swapoff/free
    mkfs/mk2fs/fsck/e2fsck/tune2fs/blkid
    parted
  • 原文地址:https://www.cnblogs.com/Axjcy/p/9475285.html
Copyright © 2011-2022 走看看