fhq treap

Posted wawawa8

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了fhq treap相关的知识,希望对你有一定的参考价值。

fhq-treap 小结

粗浅地学习了这个神奇的数据结构,下面瞎写一些感受

首先fhq treap是一个基于分裂与合并的平衡树,那么怎么分裂,怎么合并呢

我们分两种情况考虑

一、权值平衡树(我自己取的名字)

所谓权值平衡树,就是任何操作都只与权值有关的平衡树

比如最基础的分裂,合并操作

分裂就是把平衡树按照权值(k)分成两半,一边所有点的权值(leq k),另一边权值(gt k)

怎么分裂呢

首先根据(treap)的定义,所有点的权值是一颗二叉搜索树(BST),就是左边比他小,右边比他大

那么分裂的时候,我们比较当前节点和(k)的大小,如果比(k)大,那么显然右子树内全部节点都比(k)

所以我们把当前节点加上他的右子树连到第二棵树上,剩下的左子树继续分裂

如果比(k)小也是一样的

Example Code:
void split(int &x,int &y,int k,int nw){
   //x表示第一棵子树上的某个节点的某个指针,y是第二棵子树上的,nw是当前节点
   if(nw==0){x=y=0;return;}
   if(tr[x].val>k){
      y=nw;split(x,tr[nw].l,k,tr[nw].l);
   }
   else{
      x=nw;split(tr[nw].r,y,k,tr[nw].r);
   }
   update(nw);
}

然后合并就更简单了

就是把两颗子树(默认其中有一棵每个点的权值都小于另一棵任何点)合成一个

因为有上面那个条件,所以只考虑那个随机值的大小使得树满足堆的性质即可

那么比较当前两棵树中的两个当前节点:

如果第一棵树的rand值小,那么这个点连到总树上,然后这个点的左子树肯定不受到第二棵子树的影响(权值小于第二棵子树),那么递归合并这个点的右子树和第二棵子树即可

反之亦然

Example Code:
int merge(int x,int y){
   if(!x || !y) return x+y; //精妙的写法,避免一堆if
   if(tr[x].rnd<tr[y].rnd){
      tr[x].r=merge(tr[x].r,y);update(x);
      return x;
   }
   else{
      tr[y].l=merge(x,tr[y].l);update(y);
      return y;
   }
}

这两种操作都是(O(深度))的,所以复杂度是(log)级别的

那么其他的操作就是两个操作拼凑起来

比如插入一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后当前数当做第三部分,做(merge(merge(l,nw),r))即可

比如删除一个数,就是先分裂成小于等于这个数的一部分,大于这个数的一部分,然后再把第一棵树分成等于这个数和小于这个数,然后在等于这个数的树里把根删掉(merge根的两个子树),最后全部合起来就可以了

再比如求排名,可以分出小于这个数的一部分,这个部分的size就是排名了

等等。。。

但是有一个问题,就是常数比较大

比如删除操作常数可能到5~6,比起什么线段树,树状数组之类的慢多了

但是在平衡树里面算快的了。。。(垃圾数据结构)

但是这样怎么处理区间赋值,区间加这种操作呢?没有办法。。。

因为你在处理的时候一直保持有序状态,顺序就乱套了,没法取出区间

所以我们引入第二类fhq-treap

二、序列操作平衡树(仍旧是我自己取的名字。。。)

这个平衡树的时间复杂度很奇怪,反正我觉得不是(log)的,但是好像跑的挺快

有没有人会证复杂度的请留言,谢谢

这个和上面那个的唯一区别就是他不满足BST的性质(或者满足但是我没有发现?)

首先我们观察他的build函数(就是把一个无序数组(O(n))建成treap)

int build(int *data,int k){
    int nw=0,pre=0;top=0;
    rep(i,1,k){
        nw=newnode(data[i]);pre=0;
        while(top && rnd[sta[top]]>rnd[nw]){
            update(sta[top]);
            pre=sta[top];
            sta[top--]=0;
        }
        if(top) son[sta[top]][1]=nw;
        son[nw][0]=pre;sta[++top]=nw;
    }
    while(top) update(sta[top--]);
    return sta[1];
}

其中data就是这个数组,k是他的长度

sta维护的是一个栈,top是栈顶指针

newnode(x)是新建一个权值为x的节点并且返回下标

这是什么意思呢

就是说我们只关心随机值,保持他是个堆

那么我们顺着插入每一个元素

在栈里存储最右链,栈顶存储最底下的元素,栈底存储根,那么rand值显然从栈顶到栈底递减,然后我们在栈里找,直到找到第一个rand小于当前rand的元素,那么这个元素就是当前元素的父亲

然后我们假装数组是有序的,那么新加进去的元素肯定最大,所以他就是他父亲的右儿子

他父亲原来的右子树变成他的左子树(权值比他小)

然后更新最右链

从中我们显然发现不满足BST的性质(或者说下标满足BST的性质,但是我们存储的值是权值而非下标,也就是说下标并没记录,在操作几次之后就全乱了),那么复杂度是怎么回事啊。。。

先不说复杂度的事了,我们先看有哪些操作

最基础的依旧是split和merge

void split(int &x,int &y,int k,int nw){
    if(nw==0){x=y=0;return;}
    pushdown(nw);
    if(sz[son[nw][0]]>=k){
        y=nw;split(x,son[nw][0],k,son[nw][0]);
    }
    else{
        x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
    }
    update(nw);
}

int merge(int x,int y){
    if(x) pushdown(x);if(y) pushdown(y);
    if(!x || !y) return x+y;
    if(rnd[x]<rnd[y]){
        son[x][1]=merge(son[x][1],y);
        update(x);return x;
    }
    else{
        son[y][0]=merge(x,son[y][0]);
        update(y);return y;
    }
}

我们看跟之前的区别

实际上区别不太大,就是把split中val的比较变成了子树大小的比较,传进去的k指的是位置而非权值

然后注意到多了一个pushdown函数

什么鬼。。。这不是线段树操作吗。。。

好的,为了处理区间操作,我们引入了懒标记(好强啊),因为我们的treap不再按val分而是按下标分,所以一个节点的儿子也是他的子区间,和线段树类似,所以我们可以完成懒标记的下放操作

void pushdown(int x){
    if(son[x][0]) change(son[x][0],lazy[x]);
    if(son[x][1]) change(son[x][1],lazy[x]);
    lazy[x]=0;
}

事实上现在我们就讲完了所有基础操作了,可以做很多题了,就是把这些操作拼拼凑凑即可

下面我们看一道例题:

bzoj1500 维修数列

请写一个程序,要求维护一个数列,支持以下 6 种操作:

请注意,格式栏 中的下划线‘ _ ’表示实际输入文件中的空格

技术分享图片

我们一个个操作分析

首先插入操作

他并不插入一个数,他插入一堆数。。。

怎么办呢

首先插入总次数是有限制的,所以每次暴力插入应该是可行的,(不过因为复杂度本身就很奇怪所以t了也是正常的事)

但是我们有更好的插入方法

插入一个的时候,我们是merge三棵树,现在也一样嘛,就是先把这个序列建成一个treap,然后一起merge起来

因为他插入位置是连续的一段,所以merge是正确的

然后删除操作

就是拆成三颗子树,然后把中间的扔掉

然后修改

记录标记表示这个区间被修改成几了,如果为inf则没被修改

那么只要拆成三颗树,然后把根打一下标记就好了

翻转

先拆成三棵树

然后把中间的树每一层都翻转

这样就炸了

所以我们打上一个翻转标记,翻转一次就xor 1

求和

sum是很好维护的,因为修改的时候只要记录size就可以维护了,翻转的时候总的sum不变

求最大子序列

这个有点麻烦

维护三个量,首先lmx表示这个节点对应区间的左边一整段的最大和,rmx就是右边一整段的最大和,tmx表示经过这个节点的根的最大和

然后tmx[root]就是答案

更新详见代码

#include<stdio.h>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<cmath>
#include<iostream>
#include<queue>
#include<string>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef long double ld;
typedef unsigned long long ull;
typedef pair<long long,long long> pll;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rep(i,j,k)  for(register int i=(int)(j);i<=(int)(k);i++)
#define rrep(i,j,k) for(register int i=(int)(j);i>=(int)(k);i--)

ll read(){
    ll x=0,f=1;char c=getchar();
    while(c<‘0‘ || c>‘9‘){if(c==‘-‘)f=-1;c=getchar();}
    while(c>=‘0‘ && c<=‘9‘){x=x*10+c-‘0‘;c=getchar();}
    return x*f;
}

const int inf=1000000000;
const int maxn=1000100;
queue<int> trash;int n,m,a[maxn];
int sta[maxn],son[maxn][2],sz[maxn],sum[maxn],val[maxn],tmx[maxn],lmx[maxn],rmx[maxn];
int cnt,lazy_revise[maxn],lazy_reverse[maxn],rnd[maxn],top,aa[maxn],root;

void update(int x){
    int l=son[x][0],r=son[x][1];
    if(l && r){
        sz[x]=sz[l]+sz[r]+1;
        sum[x]=sum[l]+sum[r]+val[x];
        tmx[x]=max(tmx[l],tmx[r]);
        tmx[x]=max(tmx[x],rmx[l]+val[x]+lmx[r]);
        lmx[x]=max(lmx[l],sum[l]+val[x]+lmx[r]);
        rmx[x]=max(rmx[r],rmx[l]+val[x]+sum[r]);
    }
    else if(l){
        sz[x]=sz[l]+1;
        sum[x]=sum[l]+val[x];
        tmx[x]=max(tmx[l],rmx[l]+val[x]);
        lmx[x]=max(max(lmx[l],sum[l]+val[x]),0);
        rmx[x]=max(0,val[x]+rmx[l]);
    }
    else if(r){
        sz[x]=sz[r]+1;
        sum[x]=sum[r]+val[x];
        tmx[x]=max(tmx[r],lmx[r]+val[x]);
        rmx[x]=max(rmx[r],sum[r]+val[x]);
        rmx[x]=max(0,rmx[x]);
        lmx[x]=max(0,lmx[r]+val[x]);
    }
    else{
        sz[x]=1;
        sum[x]=tmx[x]=val[x];
        lmx[x]=rmx[x]=max(val[x],0);
    }
}

int newnode(int k){
    int x=0;
    if(!trash.empty()){
        x=trash.front();trash.pop();
    }
    else x=++cnt;
    son[x][0]=son[x][1]=lazy_reverse[x]=0;
    lazy_revise[x]=inf;
    rnd[x]=rand();sz[x]=1;val[x]=sum[x]=tmx[x]=k;
    lmx[x]=rmx[x]=max(k,0);return x;
}

int build(int *data,int k){
    int nw=0,pre=0;top=0;
    rep(i,1,k){
        nw=newnode(data[i]);pre=0;
        while(top && rnd[sta[top]]>rnd[nw]){
            update(sta[top]);
            pre=sta[top];
            sta[top--]=0;
        }
        if(top) son[sta[top]][1]=nw;
        son[nw][0]=pre;sta[++top]=nw;
    }
    while(top) update(sta[top--]);
    return sta[1];
}

void doit(int x){
    if(x==0) return;
    trash.push(x);
    doit(son[x][0]),doit(son[x][1]);
}

void change(int x,int k){
    val[x]=k;sum[x]=sz[x]*k;
    lmx[x]=rmx[x]=max(sum[x],0);
    tmx[x]=max(sum[x],val[x]);
    lazy_revise[x]=k;
}

void flip(int x){
    swap(son[x][0],son[x][1]);
    swap(lmx[x],rmx[x]);
    lazy_reverse[x]^=1;
}

void pushdown(int x){
    if(lazy_revise[x]!=inf){
        if(son[x][0]) change(son[x][0],lazy_revise[x]);
        if(son[x][1]) change(son[x][1],lazy_revise[x]);
    }
    if(lazy_reverse[x]!=0){
        if(son[x][0]) flip(son[x][0]);
        if(son[x][1]) flip(son[x][1]);
    }
    lazy_revise[x]=inf;lazy_reverse[x]=0;
}

void split(int &x,int &y,int k,int nw){
    if(nw==0){x=y=0;return;}
    pushdown(nw);
    if(sz[son[nw][0]]>=k){
        y=nw;split(x,son[nw][0],k,son[nw][0]);
    }
    else{
        x=nw;split(son[nw][1],y,k-sz[son[nw][0]]-1,son[nw][1]);
    }
    update(nw);
}

int merge(int x,int y){
    if(x) pushdown(x);if(y) pushdown(y);
    if(!x || !y) return x+y;
    if(rnd[x]<rnd[y]){
        son[x][1]=merge(son[x][1],y);
        update(x);return x;
    }
    else{
        son[y][0]=merge(x,son[y][0]);
        update(y);return y;
    }
}

void ins(){
    int pos=read(),len=read();
    rep(i,1,len) aa[i]=read();
    int nw=build(aa,len);
    int x,y;
    split(x,y,pos,root);
    root=merge(merge(x,nw),y);
}

void del(){
    int pos=read(),len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    root=merge(x,w);
    doit(z);
}

void upd(){
    int pos=read(),len=read(),k=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    change(z,k);root=merge(x,merge(z,w));
}

void rev(){
    int pos=read(),len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    flip(z);root=merge(x,merge(z,w));
}

void calc1(){
    int pos=read();
    int len=read();
    int x,y,z,w;
    split(x,y,pos-1,root);
    split(z,w,len,y);
    printf("%d
",sum[z]);
    root=merge(x,merge(z,w));
}

void calc2(){
    printf("%d
",tmx[root]);
}

int main(){
    srand(20020709);
    n=read();m=read();
    rep(i,1,n) a[i]=read();
    root=build(a,n);
    while(m--){
        string s;cin>>s;
        if(s[0]==‘I‘) ins();
        else if(s[0]==‘D‘) del();
        else if(s[0]==‘M‘ && s[2]==‘K‘) upd();
        else if(s[0]==‘R‘) rev();
        else if(s[0]==‘G‘) calc1();
        else calc2();
    }
    return 0;
}

以上是关于fhq treap的主要内容,如果未能解决你的问题,请参考以下文章

算法学习Fhq-Treap(无旋Treap)

浅谈fhq treap

fhq-Treap 文艺平衡树代码记录

平衡树合集(Treap,Splay,替罪羊,FHQ Treap)

模板fhq-treap

fhq-Treap原理