非旋转Treap详解

利用其他人其中考试的时间,终于学完了非旋转Treap,它与普通Treap的区别就是它不旋转废话。前置知识只有BST和可并堆。

BST看这个博客,解释的挺清楚的。https://www.cnblogs.com/jiangminghong/p/9999884.html

可并堆就是用很快的时间合并两个堆。如果裸上一个并查集的话就是nlog2n.这个复杂度我们是不能接受的。正常的可并堆是维护一棵左偏树,我们用一个参数dis[x]表示从x点出发能够向右走的最大步数。每次两个堆合并时,我们就把一个堆扔到另一个堆里就行了。

在旋转Treap中,我们用随机数来保持平衡树的平衡.非旋转Treap也是同样的做法。

1 int lson[N];//表示当前点的左儿子
2 int rson[N];//表示当前点的右儿子
3 int key[N];//表示当前点的随机数(保持平衡树的平衡)
4 int val[N];//表示当前点的权值
5 int size[N];//表示当前点子树的大小

这就是我们需要维护的信息。

看看操作吧

1.Merge【合并】O(logn)
 2.Split【拆分】O(logn)

只有两个

可资瓷使用范围

1.Insert Newnode+Merge O(logn)

2.Delete Split+Split+Merge O(logn)

3.Find_kth Split+Split O(logn)

4.Query  Split+Split  O(logn)

5.各种区间操作

1.split

split操作就是以一个点为界限,将平衡树分裂成两棵平衡树,保证左边平衡树中任意点权值小于右边平衡树中任意点的权值。

以将平衡树分裂成权值<=val和权值>val两部分为例:从根节点开始往下查找,当当前点权值<=val时,将当前点及它的右子树接到分裂后第二棵平衡树的左子树上;反之则将当前点及它的左子树接到分裂后第一棵平衡树的右子树上,直到找到叶子节点为止。

void split(int now,int k,int &x,int &y)//now表示现在的位置,k表示要查询的权值,x,y分别为拆分后两子树的根{    if(!now)    {        x=y=0;        return ;    }    if(val[now]<=k)        x=now,split(rson[now],k,rson[now],y);    else        y=now,split(lson[now],k,x,lson[now]);    push_up(now);    return ;}

如果想要拆分成前K个点与后n-K个点也是同理的,判断的条件就是size喽!

void split(int now,int k,int &x,int &y)
{
    if(!now)
    {
        x=y=0;
        return ;
    }
    if(size[lson[now]]<k)
        x=now,split(rson[now],k-size[lson[now]]-1,rson[now],y);
    else
        y=now,split(lson[now],k,x,lson[now]);
    push_up(now);
}

2.merge

merge这个操作就是将两个堆合并,我们请出可并堆。可并堆的合并方式就是按照随机数大小来合并。

int merge(int x,int y)
{
    if(!x||!y)
        return x|y;
    if(key[x]<key[y])
    {
        rson[x]=merge(rson[x],y);
        push_up(x);
        return x;
    }
    else
    {
        lson[y]=merge(x,lson[y]);
        push_up(y)
        return y;
    }
}

有木有一直好奇push_up函数啊,其实和线段树一样,维护子树的一些信息。

有了这两个sao操作,是不是就可以在序列上想干嘛就干嘛了?

1.Newnode

新建节点,加入平衡树

int newnode(int x)
{
    size[++tot]=1;
    val[tot]=x;
    key[tot]=rand()*rand();
    return tot;
}

split(root,a,x,y);root=merge(merge(x,newnode(a)),y);

2.Delete

划分成3个区间,之后合并

split(root,a,x,z);
split(x,a-1,x,y);
y=merge(lson[y],rson[y]);
root=merge(merge(x,y),z);

3.Query(查询以x为根排名第K的数)

判断K和左子树大小的关系,根据size找到答案。

int kth(int now,int k)
{
    while(1)
    {
        if(now==0)
            break;
        if(k<=size[lson[now]])
            now=lson[now];
        else if(k==size[lson[now]])
            return now;
        else
            k-=size[lson[now]]+1,now=rson[now];
    }
    return 0;
}

4.前驱&后继

split(root,a-1,x,y);
printf("%d\n",val[kth(x,size[x])]);
root=merge(x,y);
//前驱

split(root,a,x,y);
printf("%d\n",val[kth(y,1)]);
root=merge(x,y);
//后继

LuoguP3369 普通平衡树

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 1000100
int tot;
int size[N];
int lson[N];
int rson[N];
int key[N];
int val[N];
int n,m;
int root;
int x,y;
void push_up(int x){size[x]=size[lson[x]]+size[rson[x]]+1;return ;}
int newnode(int x)
{
    size[++tot]=1;
    val[tot]=x;
    key[tot]=rand();
    return tot;
}
int merge(int x,int y)
{
    if(!x||!y)
        return x+y;
    if(key[x]<key[y])
    {
        rson[x]=merge(rson[x],y);
        push_up(x);
        return x;
    }
    else
    {
        lson[y]=merge(x,lson[y]);
        push_up(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(rson[now],k,rson[now],y);
        else
            y=now,split(lson[now],k,x,lson[now]);
        push_up(now);
    }
    return ;
}
int kth(int now,int k)
{
    while(1)
    {
        if(k<=size[lson[now]])
            now=lson[now];
        else if(k==size[lson[now]]+1)
            return now;
        else
            k-=size[lson[now]]+1,now=rson[now];
    }
    return 0;
}
int main()
{
    srand(20030305);
    scanf("%d",&n);
    int opt,a;
    while(n--)
    {
        scanf("%d%d",&opt,&a);
        if(opt==1)
        {
            split(root,a,x,y);
            root=merge(merge(x,newnode(a)),y);
        }
        if(opt==2)
        {
            int z;
            split(root,a,x,z);
            split(x,a-1,x,y);
            y=merge(lson[y],rson[y]);
            root=merge(merge(x,y),z);
        }
        if(opt==3)
        {
            split(root,a-1,x,y);
            printf("%d\n",size[x]+1);
            root=merge(x,y);
        }
        if(opt==4)
            printf("%d\n",val[kth(root,a)]);
        if(opt==5)
        {
            split(root,a-1,x,y);
            printf("%d\n",val[kth(x,size[x])]);
            root=merge(x,y);
        }
        if(opt==6)
        {
            split(root,a,x,y);
            printf("%d\n",val[kth(y,1)]);
            root=merge(x,y);
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/342zhuyongqi/p/10028990.html

时间: 2024-08-02 02:55:56

非旋转Treap详解的相关文章

非旋转Treap

Treap是一种平衡二叉树,同时也是一个堆.它既具有二叉查找树的性质,也具有堆的性质.在对数据的查找.插入.删除.求第k大等操作上具有期望O(log2n)的复杂度.     Treap可以通过节点的旋转来实现其维持平衡的操作,详见旋转式Treap. 而旋转式Treap在对区间数据的操作上无能为力,这就需要非旋转式Treap来解决这些区间问题. 非旋转式Treap支持的操作 基本操作: 操作 说明 实现复杂度 Build 构造Treap O(n) Merge 合并Treap O(log2n) Sp

socket编程的同步、异步与阻塞、非阻塞示例详解

socket编程的同步.异步与阻塞.非阻塞示例详解之一 分类: 架构设计与优化 简介图 1. 基本 Linux I/O 模型的简单矩阵 每个 I/O 模型都有自己的使用模式,它们对于特定的应用程序都有自己的优点.本节将简要对其一一进行介绍. 一.同步阻塞模式在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或发生错误). /* * \brief * tcp client */ #include <stdio.h> #include <stdlib

delphi Winsock非阻塞模式详解

delphi Winsock非阻塞模式详解   Winsockt的TClientSocket设置ClientType的属性为ctNonBlocking.则通讯模式为非阻塞模式. ctBlocking为阻塞模式,这里说一下阻塞与非阻塞的一些区别. ctBlocking模式当客户端请求数据后,线程阻塞不继续执行,直到服务端返回数据,客户端将据需执行,并读取数据. 然而阻塞模式的缺陷还是比较大的,经常会使程序死掉或者假死.当服务端发送较大的文件时,阻塞模式基本废掉了,由于数据缓冲较小,不能及时的获取数

[bzoj3173]最长上升子序列_非旋转Treap

最长上升子序列 bzoj-3173 题目大意:有1-n,n个数,第i次操作是将i加入到原有序列中制定的位置,后查询当前序列中最长上升子序列长度. 注释:1<=n<=10,000,开始序列为空. 想法:显然,我们发现,我每次加入的数一定是当前序列中最大的,所以,刚刚加入的i,要么是当前序列中LIS的结尾,要么不属于LIS.根据这个性质,我们想到:在Treap中维护这样的性质,就是维护每个数加入节点的编号.然后,我们更新新节点的方式就是它的左子树和右子树的LIS取最大+1.其实最重要的就是如何加入

普通平衡树——非旋转treap

题目: 此为平衡树系列第一道:普通平衡树您需要写一种数据结构,来维护一些数,其中需要提供以下操作:1. 插入x数2. 删除x数(若有多个相同的数,因只删除一个)3. 查询x数的排名(若有多个相同的数,因输出最小的排名)4. 查询排名为x的数5. 求x的前驱(前驱定义为小于x,且最大的数)6. 求x的后继(后继定义为大于x,且最小的数) n<=100000 所有数字均在-107到107内. 输入样例: 10 1 106465 4 1 1 317721 1 460929 1 644985 1 841

4923: [Lydsy1706月赛]K小值查询 平衡树 非旋转Treap

国际惯例的题面:这种维护排序序列,严格大于的进行操作的题都很套路......我们按照[0,k],(k,2k],(2k,inf)分类讨论一下就好.显然第一个区间的不会变化,第二个区间的会被平移进第一个区间,第三个区间的相对大小不会变化.于是我们直接把第二个区间拆了重构,一个一个插入第一个区间即可.因为每次这样做最少减半,所以每个元素只会被重构log次,复杂度nlog^2n.这种按照值域分离区间的操作,非旋转treap实现起来是最简单的......然而第一次写非旋转treap还是出了一点问题,注意它

平衡树讲解(旋转treap,非旋转treap,splay)

在刷了许多道平衡树的题之后,对平衡树有了较为深入的理解,在这里和大家分享一下,希望对大家学习平衡树能有帮助. 平衡树有好多种,比如treap,splay,红黑树,STL中的set.在这里只介绍几种常用的:treap和splay(其中treap包括旋转treap和非旋转treap). 一.treap treap这个词是由tree和heap组合而成,意思是树上的的堆(其实就是字面意思啦qwq).treap可以说是由二叉搜索树(BST)进化而来,二叉搜索树每个点满足它左子树中所有点权值都比它小,它右子

[bzoj1895][Pku3580]supermemo_非旋转Treap

supermemo bzoj-1895 Pku-3580 题目大意:给定一个n个数的序列,需支持:区间加,区间翻转,区间平移,单点插入,单点删除,查询区间最小值. 注释:$1\le n\le 6.1\cdot 10^6$. 想法: 这数据范围给的我真是醉了. 显然用平衡树,这里用非旋转Treap,题目让你干什么你就干什么. 区间加:撕出子树区间后打标记维护区间加. 区间翻转:撕出子树区间后打标记维护区间翻转. 区间平移:相当于两段相邻区间交换,所以撕成四部分:左边,第一个区间,第二个区间,右边.

“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第二十八章:公平锁与非公平锁详解 下一章 "全栈2019"Java多线程第二十九章:可重入锁与不可重入锁详解 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"J