fhq_treap 学习笔记

前言:昨天写NOIp2017队列,写+调辗转了3h+,不知道怎么的,就点进了一个神仙的链接,便在今日学习了神仙的fhq_treap。

简介:fhq_treap功能强大,支持splay支持的所有操作,代码简单,仅有两个核心函数\(merge\)\(split\),更重要的是,它作为无旋的平衡树,是支持可持久化的。

正题:fhq_treap的实现

(0)树的域

对树的每个节点,我们维护
\(ch[2],siz,dat,val\)
分别为左右儿子,子树大小,BST性质的关键字,堆性质的关键字
其中堆是小根堆

(1)split函数

\(void \ split(now,k,\&x,\&y)\)

按照\(k\)值的意义可以有两种方式理解,其一为把以\(now\)为根的子树按照\(BST\)性质的权值按不大于\(k\)和大于\(k\)两种分裂成两颗子树,两颗子树的根分别为\(x\)\(y\)

这个函数是递归执行的,有严格的子问题划分性。

我们可以这样描述一次对整棵树的操作:

比较当前根节点权值\(dat[now]\)\(k\)
若大于,则根节点和右子树被划分为\(y\)树,当前根为\(now\)\(y\)树的左子树不确定,进入左子树求解子问题
若不大于,则根节点和左子树被划分为\(x\)树,当前根为\(now\)\(x\)树的右子树不确定,进入右子树求解子问题

复杂度分析:进入节点所构成的路径相当于对平衡树做了一次查询值为\(k\)的点的操作,复杂度与树的深度相关,根据\(treap\)的随机性,可以认为是\(O(logn)\)

代码实现:

void split(int now,int k,int &x,int &y)//把小于等于k的树分在x上
{
    if(!now) {x=y=0;return;}
    if(dat[now]>k) y=now,split(ls,k,x,ls);
    else x=now,split(rs,k,rs,y);
    updata(now);
}

其中,\(ls\),\(rs\)为宏定义的左右儿子,\(updata\)为更新节点的大小\(siz\)

另一种\(k\)值的意义为树中排名为\(k\)的节点,实现起来差不多

(2)merge函数

\(int \ merge(int \ x,int \ y)\)

意义为:把以\(x\)为根的子树和以\(y\)值为根的子树合并,返回新根节点。

注意这里有要求:以\(x\)值为根的子树的BST性质的最大节点小于以\(y\)为根的子树的最小节点。也就是说,这两棵子树本来就是在BST是一颗完全小于另外一颗,我们只需要在合并时保证堆性质即可。

这个函数是仍然递归执行的,有严格的子问题划分性。

我们可以这样描述一次对整棵树的操作:
比较两个根节点的\(val\)
\(val[x]<val[y]\),好的当前根就是\(x\)了,\(x\)的左子树不用管了,我们进入\(x\)的右子树和\(y\)继续玩
否则,差不多啊反过来就行了

复杂度分析:两颗子树你一下我一下的走到了底,复杂度与子树的深度之和相关,可以认为是\(O(logn)\)

代码实现:

int Merge(int x,int y)//左树的权值小于右树
{
    if(!x||!y) return x+y;
    if(val[x]<val[y])
    {
        ch[x][1]=Merge(ch[x][1],y);
        updata(x);
        return x;
    }
    else
    {
        ch[y][0]=Merge(x,ch[y][0]);
        updata(y);
        return y;
    }
}

(3)其他常见操作

有了功能强大的\(split\)\(merge\),我们就可以很轻松的执行其他操作了

\(insert(k)\)

实现:把树按\(k\)分裂成两个,新建一个点再合并回去

代码:

void Insert(int k)
{
    int x,y;
    split(root,k,x,y);
    root=Merge(Merge(x,New(k)),y);
}

\(extrack(k)\)//删除

实现:把树先分裂成两个,再把含\(k\)的那一颗把\(k\)单独分出来。注意如果有重复元素,单独的\(k\)构成的子树只删除根节点就够了。最后合并回去。

代码:

void extrack(int k)
{
    int x,y,z;
    split(root,k,x,y);
    split(x,k-1,x,z);
    z=Merge(ch[z][0],ch[z][1]);
    root=Merge(x,Merge(z,y));
}

(4) 实例 洛谷P3369普通平衡树

Code:

#include <cstdio>
#include <cstdlib>
#define ls ch[now][0]
#define rs ch[now][1]
const int N=100010;
int siz[N],ch[N][2],dat[N],val[N],tot,root;
void updata(int now)
{
    siz[now]=siz[ls]+siz[rs]+1;
}
void split(int now,int k,int &x,int &y)//把小于等于k的树分在x上
{
    if(!now) {x=y=0;return;}
    if(dat[now]>k) y=now,split(ls,k,x,ls);
    else x=now,split(rs,k,rs,y);
    updata(now);
}
int Merge(int x,int y)//左树的权值小于右树
{
    if(!x||!y) return x+y;
    if(val[x]<val[y])
    {
        ch[x][1]=Merge(ch[x][1],y);
        updata(x);
        return x;
    }
    else
    {
        ch[y][0]=Merge(x,ch[y][0]);
        updata(y);
        return y;
    }
}
int New(int k)
{
    siz[++tot]=1;dat[tot]=k;val[tot]=rand();
    return tot;
}
void Insert(int k)
{
    int x,y;
    split(root,k,x,y);
    root=Merge(Merge(x,New(k)),y);
}
void extrack(int k)
{
    int x,y,z;
    split(root,k,x,y);
    split(x,k-1,x,z);
    z=Merge(ch[z][0],ch[z][1]);
    root=Merge(x,Merge(z,y));
}
void Rank(int k)
{
    int x,y;
    split(root,k-1,x,y);
    printf("%d\n",siz[x]+1);
    root=Merge(x,y);
}
void frank(int now,int x)
{
    while(233)
    {
        if(siz[ls]>=x) now=ls;
        else if(siz[ls]+1<x) x-=siz[ls]+1,now=rs;
        else {printf("%d\n",dat[now]);return;}
    }
}
void pre(int k)
{
    int x,y;
    split(root,k-1,x,y);
    frank(x,siz[x]);
    root=Merge(x,y);
}
void suc(int k)
{
    int x,y;
    split(root,k,x,y);
    frank(y,1);
    root=Merge(x,y);
}
int main()
{
    int t,opt,x;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&opt,&x);
        if(opt==1) Insert(x);
        else if(opt==2) extrack(x);
        else if(opt==3) Rank(x);
        else if(opt==4) frank(root,x);
        else if(opt==5) pre(x);
        else suc(x);
    }
    return 0;
}



2018.7.28

posted @ 2018-07-28 15:25  露迭月  阅读(428)  评论(6编辑  收藏  举报