浅谈fhq treap

Posted 日拱一卒 功不唐捐

tags:

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

一、简介

fhq treap 与一般的treap主要有3点不同

1、不用旋转

2、以merge和split为核心操作,通过它们的组合实现平衡树的所有操作

3、可以可持久化

二、核心操作

代码中val表示节点权值,pri表示节点的优先级,维护小根堆

1、split

将1个treap分裂为两个treap

分裂主要有两种:以权值k作为分界点、以位置k作为分界点

①以权值k作为分界点

设原来的treap根节点为root,分裂后的<=k的treap A 的根节点为x,>k的treap B 的根节点为y

当前节点指针now从root开始,在treap A 的x位置加点,treap B 的y位置加点

若now的权值<=k,那么以now为根的左子树全部包含在A中,

把now给x,去now的右子树,x变成x的右子树,y还是y

若now的权值>k,那么以now为分的右子树全部包含在B中,

把now给y,去now的左子树,x还是x,y变成y的右子树

void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(val[now]<=k) 
        {
            x=now;
            split(ch[now][1],k,ch[now][1],y);
        }
        else
        {
            y=now;
            split(ch[now][0],k,x,ch[now][0]);
        }
        update(now);
    }
}

②以位置k作为分界点

设原来的treap根节点为root,分裂后的前k个节点的treap A 的根节点为x,剩下的节点的treap B 的根节点为y

判断放在哪颗treap的标准改成k和子树大小的比较,原理同上

void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(k<=siz[ch[now][0]])
        {
            y=now;
            split(ch[now][0],k,x,ch[now][0]);
        }
        else
        {
            x=now;
            split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y);
        }
        update(now);
    }
}

2、merge

合并以x为根的treap A 和 以y为根的treap B

前提:x的权值<y的权值

注意在merge的过程中,维护好堆和二叉搜索树的性质

若优先级x<y,

维护堆的性质-->y成为x的子节点

维护二叉搜索树的性质-->y在x的右子树

若优先级x>y,

维护堆的性质-->x成为y的子节点

维护二叉搜索树的性质-->x在y的左子树

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

三、单点操作

1、建树

建树有两种方式:nlogn 一个一个插、n 直接建

这里主要说后者

每次取mid作为当前子树的根节点即可

int new_node(int v)
{
    siz[++tot]=1;
    val[tot]=v;
    pri[tot]=rand();
    return tot;
}

int build(int l,int r)
{
    if(l>r) return 0;
    int mid=l+r>>1,v=val[mid];
    int now=new_node(v);
    ch[now][0]=build(l,mid-1);
    ch[now][1]=build(mid+1,r);
    update(now);
    return now;
}

2、插入

添加权值为v的点

1、按权值以v为分界点,split成两棵treapA(<=v),B( >v )

2、合并A和v,得到C

3、合并C和B

merge要求x的权值<y决定了 合并的顺序

split(root,v,x,y);
root=merge(merge(x,new_node(v)),y);

3、删除

删除一个权值为v的点

1、按权值以v为分界点,split成两棵treap A(<=v),B( >v)

2、把A按权值以v-1为分界点,split成两棵treap C(<=v-1) ,D(=v)

3、合并D的根节点的左右子树,相当于删除根节点,得到treap E

4、合并C和E,得F

5、合并F和B

split(root,v,x,z);
split(x,v-1,x,y);
y=merge(ch[y][0],ch[y][1]);
root=merge(merge(x,y),z);

4、查询v的排名

1、按权值以v-1为分界点,split成两棵treap A(<=v-1)  B(>=v)

2、A的大小+1 即为权值v的排名

3、合并A、B

split(root,v-1,x,y);
cout<<siz[x]+1<<\n;
root=merge(x,y);

5、查询排名为x的数

int get_kth(int now,int k)
{
    while(1)
    {
        if(k<=siz[ch[now][0]]) now=ch[now][0];
        else
        {
            k-=siz[ch[now][0]];
            if(k==1) return now;
            k--;
            now=ch[now][1];
        }
    }
}

cout<<val[get_kth(root,v)]<<\n;

6、查找比v小的最大的数(前驱)

1、按权值以v-1为分界点,split成两棵treap A(<=v-1),B(>=v)

2、在A中查找A的排名最靠后的数,输出

3、合并A和B

split(root,v-1,x,y);
cout<<val[get_kth(x,siz[x])]<<\n;
root=merge(x,y);

7、查询比v大的最小的数(后继)

1、按权值以v为分界点,split成两棵treap A(<=v),B(>v)

2、在B中找最靠前的数,输出

3、合并A和B

 

split(root,v,x,y);
cout<<val[get_kth(y,1)]<<\n;
root=merge(x,y);

 

四、区间操作

对区间[l,r]执行某种操作

1、以位置r作为分界点,split成两棵treap A(前r个节点)、B(位置r之后的节点)

2、在A中以位置l-1为分界点,split成两棵treap C(前l-1个节点)、D(第l到第r个节点)

3、对D执行操作

注意因为执行区间操作,增加2个虚拟节点,所有节点后移一位

split(root,r+1,a,b);
split(a,l,c,d);
work(d)

五、模板

1、普通平衡树

https://www.luogu.org/problemnew/show/P3369

技术分享图片
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace std;

#define N 100001

int root;

int tot;
int fa[N],ch[N][2],val[N],pri[N],siz[N];

void read(int &x)
{
    x=0; int f=1; char c=getchar();
    while(!isdigit(c))  { if(c==-) f=-1; c=getchar(); }
    while(isdigit(c)) { x=x*10+c-0; c=getchar(); }
    x*=f;
}

void update(int x)
{
    siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
}

int new_node(int v)
{
    siz[++tot]=1;
    val[tot]=v;
    pri[tot]=rand();
    return tot;
}

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

void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(val[now]<=k) 
        {
            x=now;
            split(ch[now][1],k,ch[now][1],y);
        }
        else
        {
            y=now;
            split(ch[now][0],k,x,ch[now][0]);
        }
        update(now);
    }
}

int get_kth(int now,int k)
{
    while(1)
    {
        if(k<=siz[ch[now][0]]) now=ch[now][0];
        else
        {
            k-=siz[ch[now][0]];
            if(k==1) return now;
            k--;
            now=ch[now][1];
        }
    }
}

int main()
{
    srand(time(0)+20001024);
    int n,u,v;
    int x,y,z;
    read(n);
    while(n--)
    {
        read(u); read(v);
        if(u==1)
        {
            split(root,v,x,y);
            root=merge(merge(x,new_node(v)),y);
        }
        else if(u==2)
        {
            split(root,v,x,z);
            split(x,v-1,x,y);
            y=merge(ch[y][0],ch[y][1]);
            root=merge(merge(x,y),z);
        }
        else if(u==3)
        {
            split(root,v-1,x,y);
            cout<<siz[x]+1<<\n;
            root=merge(x,y);
        }
        else if(u==4)
        {
            cout<<val[get_kth(root,v)]<<\n;
        }
        else if(u==5)
        {
            split(root,v-1,x,y);
            cout<<val[get_kth(x,siz[x])]<<\n;
            root=merge(x,y);
        }
        else
        {
            split(root,v,x,y);
            cout<<val[get_kth(y,1)]<<\n;
            root=merge(x,y);
        }
    }
}
View Code

2、文艺平衡树

https://www.luogu.org/problemnew/show/P3391

技术分享图片
#include<ctime>
#include<cstdio>
#include<cstdlib>
#include<iostream>

using namespace  std;

#define N 100005

int n;

int tot,root;

int siz[N],ch[N][2],val[N],pri[N];
bool rev[N];

void read(int &x)
{
    x=0; int f=1; char c=getchar();
    while(!isdigit(c))  { if(c==-) f=-1; c=getchar(); }
    while(isdigit(c)) { x=x*10+c-0; c=getchar(); }
    x*=f;
}

int new_node(int v)
{
    siz[++tot]=1;
    val[tot]=v;
    pri[tot]=rand();
    return tot;
}

void update(int x)
{
    siz[x]=1+siz[ch[x][0]]+siz[ch[x][1]];
}

int build(int l,int r)
{
    if(l>r) return 0;
    int mid=l+r>>1,v=mid-1;
    int now=new_node(v);
    ch[now][0]=build(l,mid-1);
    ch[now][1]=build(mid+1,r);
    update(now);
    return now;
}

void down(int x)
{
    rev[x]^=1;
    swap(ch[x][0],ch[x][1]);
    rev[ch[x][0]]^=1;
    rev[ch[x][1]]^=1;
}

int merge(int x,int y)
{
    if(!x || !y) return x+y;
    if(rev[x]) down(x);
    if(rev[y]) down(y);
    if(pri[x]<pri[y])
    {
        ch[x][1]=merge(ch[x][1],y);
        update(x);
        return x;
    } 
    else
    {
        ch[y][0]=merge(x,ch[y][0]);
        update(y);
        return y;
    } 
}

void split(int now,int k,int &x,int &y)
{
    if(!now) x=y=0;
    else
    {
        if(rev[now]) down(now);
        if(k<=siz[ch[now][0]])
        {
            y=now;
            split(ch[now][0],k,x,ch[now][0]);
        }
        else
        {
            x=now;
            split(ch[now][1],k-siz[ch[now][0]]-1,ch[now][1],y);
        }
        update(now);
    }
}

void reverse(int l,int r)
{
    int a,b,c,d;
    split(root,r+1,a,b);
    split(a,l,c,d);
    rev[d]^=1;
    root=merge(merge(c,d),b);
}

void dfs(int x)
{
    if(rev[x]) down(x);
    if(ch[x][0]) dfs(ch[x][0]);
    if(val[x]>=1 && val[x]<=n) cout<<val[x]<< ;
    if(ch[x][1]) dfs(ch[x][1]);
}

int main()
{
    srand(time(0)+20001024);
    int m;
    read(n); read(m);
    root=build(1,n+2);
    int l,r;
    while(m--)
    {
        read(l); read(r);
        reverse(l,r);
    }
    dfs(root);
}
View Code

 

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

浅谈fhq_treap

fhq treap

全网最详细的fhq treap (非旋treap)讲解

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

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

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