可持久化专题(一)——浅谈主席树:可持久化线段树-程序员宅基地

技术标签: 可持久化  主席树  

前言

不得不说,可持久化数据结构真是太难了!

由于数据结构这东西真的太玄学了,学这个主席树我真的学了很久。


简介

主席树为什么叫主席树?据说因为它是一个名字缩写为 H J T HJT HJT的神犇发明的,与当时主席的名字缩写一样…

主席树实质上就是一棵可持久化线段树,它的具体实现可以看下面。


让我们从值域线段树开始说起

要学主席树,我们就要先学值域线段树

值域线段树的区间存的并不是节点信息,而是在值在某一范围内的数的个数

这里写图片描述

如图就是一棵值域线段树,其中1号节点存储的是大于等于1小于等于4的数字个数,2号节点存储的是大于等于1小于等于2的数字个数,3号节点存储的是大于等于3小于等于4的数字个数,4号节点存储的是等于1的数字个数,5号节点存储的是等于2的数字个数,6号节点存储的是等于3的数字个数,7号节点存储的是等于4的数字个数。

值域线段树的查询也挺简单的,若要查询这段区间内的第 k k k大,只要比较当前元素的左子树大小加1(1是当前元素本身的大小)与询问的 k k k,若大于等于,就访问左子树,否则将 k k k减去当前元素的左子树大小加1,然后访问右子树。

这和平衡树有什么区别!!!

还有一个问题,就是值域线段树存储的区间范围是固定的,所以如果要查询区间第 k k k大,我们就不能只用一棵值域线段树。

考虑建 n n n棵值域线段树,每棵值域线段树存储区间 [ 1 , i ] [1,i] [1,i]的信息,这样一来,要查询 [ l , r ] [l,r] [l,r]的第 k k k大时,只要在查询的过程中,将第 r r r棵值域线段树的信息减去第 l − 1 l-1 l1棵值域线段树的信息即可,这利用了前缀和的思想。
或许你会问,这有什么用?建 n n n棵树,内存那么大,我平衡树第一个不服!

好吧,不服就不服,值域线段树还是有点用的,因为平衡树没法可持久化啊(**可持久化 T r e a p Treap Treap**请走开)!


从值域线段树到主席树

知道了值域线段树,我们就可以开始尝试实现主席树了。

来研究一下下面两棵分别存储 [ 1 , 3 ] [1,3] [1,3] [ 1 , 4 ] [1,4] [1,4]区间信息的值域线段树(圆圈中为以该节点为根的子树大小)。

这里写图片描述

仔细观察可得,我们每次新加入一个节点,有影响的只有图中 标 红 \color{red}{标红} 的节点。

再仔细观察一下,这些节点都在一条链上(废话)。

那么,我们就会有一个大胆的想法:可不可以每次只新建一条链而不是一棵树,就像下面这样?

这里写图片描述

这就是传说中的主席树了。


主席树的具体实现

当然,真正实现主席树时,还是有一些细节要注意的。

这里就不多讲了,直接上代码吧:(洛谷板子题

#include<bits/stdc++.h>
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define LL long long
#define swap(x,y) (x^=y,y^=x,x^=y)
#define tc() (A==B&&(B=(A=ff)+fread(ff,1,100000,stdin),A==B)?EOF:*A++)
#define pc(ch) (pp_<100000?pp[pp_++]=(ch):(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=(ch)))
#define N 200000 
int pp_=0;char ff[100000],*A=ff,*B=ff,pp[100000];
using namespace std;
int n,Q,m,tot=0,rt[N+5],a[N+5],p[N+5];
struct Chairman_Tree
{
    
    int Son[2],Size;
}node[N<<6];
inline void read(int &x)
{
    
    x=0;int f=1;char ch;
    while(!isdigit(ch=tc())) f=ch^'-'?1:-1;
    while(x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc()));
    x*=f;
}
inline void write(int x)
{
    
    if(x<0) pc('-'),x=-x;
    if(x>9) write(x/10);
    pc(x%10+'0');
}
inline void Build(int &rt,int l,int r)//建出一棵初始时的的树,和传统的线段树几乎一样
{
    
    rt=++tot;//新建一个节点,动态开点也是主席树中特别重要的
    int mid=l+r>>1;
    if(!(l^r)) return;
    Build(node[rt].Son[0],l,mid),Build(node[rt].Son[1],mid+1,r);//分别建树
}
inline void NewPoint(int &rt,int lst,int l,int r,int val)//新建一个节点(准确来说,应该是新建一条链)
{
    
    node[rt=++tot]=node[lst],++node[rt].Size;//动态开点,先复制原先的节点,然后将子树大小加1
    int mid=l+r>>1;
    if(!(l^r)) return;
    if(val<=mid) NewPoint(node[rt].Son[0],node[lst].Son[0],l,mid,val);//如果插入的新值比当前元素小(或等于),那么就新建一个左儿子
    else NewPoint(node[rt].Son[1],node[lst].Son[1],mid+1,r,val);//否则,新建一个右儿子
}
inline int Query(int rt1,int rt2,int l,int r,int k)//区间查询,相当于同时在两棵值域线段树上询问
{
    
    int mid=l+r>>1;
    if(!(l^r)) return l;//如果l与r相等,就返回l
    if(node[node[rt2].Son[0]].Size-node[node[rt1].Son[0]].Size>=k) return Query(node[rt1].Son[0],node[rt2].Son[0],l,mid,k);//如果当前左子树大小加1大于等于询问的k,那么访问左子树
	else return Query(node[rt1].Son[1],node[rt2].Son[1],mid+1,r,k-node[node[rt2].Son[0]].Size+node[node[rt1].Son[0]].Size);//否则,将k减去当前左子树大小加1
}
inline int num(int x)//求出一个数离散化后的值,一个二分的过程
{
    
    int l=1,r=m;
    while(l<=r)
    {
    
        int mid=l+r>>1;
        if(p[mid]==x) return mid;
        else if(p[mid]>x) r=mid-1;
        else l=mid+1;
    }
}
int main()
{
    
    register int i;
    for(read(n),read(Q),i=1;i<=n;++i) read(a[i]),p[i]=a[i];
    for(sort(p+1,p+n+1),Build(rt[0],i=1,m=unique(p+1,p+n+1)-p-1);i<=n;++i) NewPoint(rt[i],rt[i-1],1,m,num(a[i]));//将元素离散化,新建一棵树以及n条链,注意存储每条链的根节点的编号
    for(i=1;i<=Q;++i)//询问
    {
    
        int x,y,k;
        read(x),read(y),read(k),write(p[Query(rt[x-1],rt[y],1,m,k)]),pc('\n');//利用前缀和思想
    }
    return fwrite(pp,1,pp_,stdout),0;
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/chenxiaoran666/article/details/81501461

智能推荐

oracle 12c 集群安装后的检查_12c查看crs状态-程序员宅基地

文章浏览阅读1.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态

解决jupyter notebook无法找到虚拟环境的问题_jupyter没有pytorch环境-程序员宅基地

文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境

国内安装scoop的保姆教程_scoop-cn-程序员宅基地

文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn

Element ui colorpicker在Vue中的使用_vue el-color-picker-程序员宅基地

文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker

迅为iTOP-4412精英版之烧写内核移植后的镜像_exynos 4412 刷机-程序员宅基地

文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机

Linux系统配置jdk_linux配置jdk-程序员宅基地

文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk

随便推点

matlab(4):特殊符号的输入_matlab微米怎么输入-程序员宅基地

文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入

C语言程序设计-文件(打开与关闭、顺序、二进制读写)-程序员宅基地

文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。‍ Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。

Touchdesigner自学笔记之三_touchdesigner怎么让一个模型跟着鼠标移动-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动

【附源码】基于java的校园停车场管理系统的设计与实现61m0e9计算机毕设SSM_基于java技术的停车场管理系统实现与设计-程序员宅基地

文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计

Android系统播放器MediaPlayer源码分析_android多媒体播放源码分析 时序图-程序员宅基地

文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;amp;gt;Jni-&amp;amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图

java 数据结构与算法 ——快速排序法-程序员宅基地

文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法