浅谈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);
        }
    }
}

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);
}

原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/8288818.html

时间: 2024-10-31 03:17:11

浅谈fhq treap的相关文章

浅谈可持久化数据结构

Preface 由于我真的是太弱了,所以真的是浅谈. 神奇的数据结构其实我也很虚啊! 值域线段树 简单的说,值域线段树区间里面存的是在这个区间内的数的个数有多少个. 有没有感觉很简单,考虑一下如果我们有一棵这样的线段树,查找排名为rk的数时只需要看一下左子树的大小就可以判断在左边还是右边了. 有没有感觉很像BST 动态开点与可持久化 根据上面的值域线段树,我们可以得出一种naive的做法: 对于每一个前缀\([1,i](i\in[1,n])\)都开一棵值域线段树,然后查询区间的时候直接每个节点的

.net中对象序列化技术浅谈

.net中对象序列化技术浅谈 2009-03-11 阅读2756评论2 序列化是将对象状态转换为可保持或传输的格式的过程.与序列化相对的是反序列化,它将流转换为对象.这两个过程结合起来,可以轻松地存储和传输数 据.例如,可以序列化一个对象,然后使用 HTTP 通过 Internet 在客户端和服务器之间传输该对象.反之,反序列化根据流重新构造对象.此外还可以将对象序列化后保存到本地,再次运行的时候可以从本地文件 中“恢复”对象到序列化之前的状态.在.net中有提供了几种序列化的方式:二进制序列化

浅谈——页面静态化

现在互联网发展越来越迅速,对网站的性能要求越来越高,也就是如何应对高并发量.像12306需要应付上亿人同时来抢票,淘宝双十一--所以,如何提高网站的性能,是做网站都需要考虑的. 首先网站性能优化的方面有很多:1,使用缓存,最传统的一级二级缓存:2,将服务和数据库分开,使用不同的服务器,分工更加明确,效率更加高:3,分布式,提供多台服务器,利用反向代理服务器nginx进行反向代理,将请求分散开来:4,数据库的读写分离,不同的数据库,将读操作和写操作分开,并实时同步即可:5,分布式缓存,使用memc

单页应用SEO浅谈

单页应用SEO浅谈 前言 单页应用(Single Page Application)越来越受web开发者欢迎,单页应用的体验可以模拟原生应用,一次开发,多端兼容.单页应用并不是一个全新发明的技术,而是随着互联网的发展,满足用户体验的一种综合技术. SEO 一直以来,搜索引擎优化(SEO)是开发者容易忽略的部分.SEO是针对搜索(Google.百度.雅虎搜索等)在技术细节上的优化,例如语义.搜索关键词与内容相关性.收录量.搜索排名等.SEO也是同行.市场竞争常用的的营销手段.Google.百度的搜

浅谈html标签

浅谈html各常用标签用法 标题标签:<h1>-<h6>来表示,使标题字体变粗. <br />换行标记 <hr />水平分隔符 &nbsp空格符 &copy版权符 <a href>a标签超链接 href可接链接地址 <p>段落标签<blockquote>引用标签及可用做缩进 <table>表格中的<ul>无序列表<ol>有序列表<dl>自定义列表<row

浅谈二维中的树状数组与线段树

一般来说,树状数组可以实现的东西线段树均可胜任,实际应用中也是如此.但是在二维中,线段树的操作变得太过复杂,更新子矩阵时第一维的lazy标记更是麻烦到不行. 但是树状数组在某些询问中又无法胜任,如最值等不符合区间减法的询问.此时就需要根据线段树与树状数组的优缺点来选择了. 做一下基本操作的对比,如下图. 因为线段树为自上向下更新,从而可以使用lazy标记使得矩阵的更新变的高校起来,几个不足就是代码长,代码长和代码长. 对于将将矩阵内元素变为某个值,因为树状数组自下向上更新,且要满足区间加法等限制

[nRF51822] 14、浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(科普类干货)

蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到耳机.游戏手柄.音响.电视, 再到手环.电子秤.智能医疗器械(血糖仪.数字血压计.血气计.数字脉搏/心率监视器.数字体温计.耳温枪.皮肤水分计等), 再到智能家居等领域均占有一席之地. 而蓝牙低功耗(BLE)是在蓝牙4.0协议上修改以适用低功耗应用场景的一种蓝牙协议. 随着上一股智能消费类电子大潮的到来,BLE的各种应用也像雨后春笋般在市场上铺开. 如果想 紧跟蓝牙协议的最新动态 ,可以在https://www.b

浅谈C++容器动态内存管理的优化

在信息学竞赛中,C++的容器的用途非常广泛,但经常因常数过大而超时.怎样才能提高它们的效率呢? 我们知道,容器是存储同一类对象的对象,既然"对象"我们无法改变,那么我们只能从"存储"入手,不难想到,不同容器在实现上的根本区别是它们对应着不同的内存组织方式,内存管理无疑是这种实现的核心,所以优化内存管理是加快容器效率的最好途径之一. 一.内存分配器简介 怎样才能优化内存管理呢?很简单,C++为我们提供了这样的接口,我们可以通过自定义容器模板中的最后一个allocato

张小龙浅谈微信公众平台的意义

腾讯高级副总裁张小龙表示:微信公众平台,就是在移动互联网时代,让企业和个人以更简捷的形式提供服务给有需要的人. 张小龙浅谈微信公众平台的意义,布布扣,bubuko.com