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

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

前言

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

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


简介

主席树为什么叫主席树?据说因为它是一个名字缩写为 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

智能推荐

顺序性容器(vector&list&deque)_vector list 赋值顺序-程序员宅基地

文章浏览阅读313次。引言(1)vector向量相当于一个数组 在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。STL内部实现时,首先分配一个非常大的内存空间预备进行存储,即capacity()函数返回的大小,当超过此分配的空间时再整体重新放分配一块内存存储,这给人以vector可以不指定vector即一个连续内存的大小的感觉。通常此默认的内存分配能完成大部分情况下的存_vector list 赋值顺序

cube/SSAS 数据级权限解决方案----不使用域身份验证-程序员宅基地

文章浏览阅读301次。一个多月没更新博客了,不是不想写而是真的没有什么高质量的文章与大家分享,最近在做一个集团公司的BI解决方案,客户提出了一个合理但是又比较棘手的一个问题,就是北京的经理和南京的经理都有权限访问公司内部的报表,但是需要对其进行透明化的权限区分,北京的区域经理不能浏览任何关于南京区域的销售信息。这个需求是很合理的,但是客户方不一定会在一个域中,也就是这个经理可能到美国到非洲(呵呵,当然这是比喻..._没有加域怎么连ssas

gradle下载的缓存路径-程序员宅基地

文章浏览阅读368次。为什么80%的码农都做不了架构师?>>> ..._download_jsc.gradle下载路径

如何在iPhone或iPad上安装iOS 11 Beta-程序员宅基地

文章浏览阅读2.6k次。The public beta of iOS 11 is now available for iPhones and iPads. Anyone who wants to play withiOS 11’s new featurescan install it today. However, we recommend backing up your device first so you ca..._ios11bate

UE4_UE5结合offline voice recognition插件做语音识别功能_ue5语音识别-程序员宅基地

文章浏览阅读6.2k次,点赞4次,收藏24次。市面上主流的语音识别大多是用科大讯飞的SDK,但是那个也不是完全免费使用的,于是我选择使用offline voice recognition的语音识别,购买插件终生使用。offline voice recognition插件在UE官方商城卖200多元。我将它需要的资源都打包成一个rar,分享给有需要的人。其中就有两个UE工程,一个是UE4.27版本的,另外一个是UE5的版本。并且也下载了两个中文的语言包,一个是简版,另外一个是完整版,对于 只是做简单的主意指令的只需要用简版的语言包即可,大大提升识别速度。第_ue5语音识别

联想计算机哪款好用,华为和联想电脑哪个好用-程序员宅基地

文章浏览阅读1.8k次。对于一些具有电脑购买需求的朋友来说,在选择购买的时候经常会有一个困惑,那就是华为的电脑和联想的电脑哪一个好用?如果您现在也存在着这样的问题,那么不妨一起来看一下下面对于这两个品牌电脑的相关介绍。联想是比较早生产电脑的一家厂商,本身也是依靠做台式电脑和笔记本电脑起家的,所以也在经过长时间的发展之后一定有成熟的一面,我的一个朋友在多年之前购买了一台联想台式电脑,现在他仍然还在用着这一款台式电脑,由此可..._联想电脑和华为电脑哪个好

随便推点

CentOS 7.X 源码编译安装MariaDB-10.2.X_group ‘mail’ not found-程序员宅基地

文章浏览阅读2.5k次。CentOS 7 编译安装MariaDB-10.2.XCentOS 7下mariadb-10.1.22 源码编译安装过程笔记,希望对大家有帮助。 下载文件https://mariadb.com/ 或 https://downloads.mariadb.org/mariadb/10.2.11/
源码包的下载下载链接: https://mirrors.tuna.tsinghua.edu.cn/m_group ‘mail’ not found

wps中的大客户版本_wps 大客户版-程序员宅基地

文章浏览阅读462次,点赞11次,收藏8次。网上的WPS各个版本基本都带有稻香插件,广告一堆堆,用户需要注册才能减少广告的显示,但依然时不时跳出来显示。其实WPS给大学做过一些版本,这种定制版本应用于大学院系的文档编写使用。最关键的是这个版本没有广告,没有稻香,没有推荐模板,只有纯净的WPS。界面虽然不华丽,风格还是很传统office2000风格,不是烦人的多标签页,也不会隐藏。工具栏不会藏着掖着直接横排展示所有图标,鼠标不用切换页,所有工具一目了然。这集美大学版本是办公软件的不二之选。_wps 大客户版

Android自动化页面测速在美团的实践-程序员宅基地

文章浏览阅读587次,点赞8次,收藏19次。我们都知道ViewPager的Tab切换是可以通过一个 OnPageChangeListener 对象进行监听的,所以我们可以为ViewPager添加一个自定义的Listener对象,在切换时记录一个时间,这样可以通过用这个时间减去页面创建后的时间得出这个多余的等待时间,上报时在总时间中减去即可。这里的 getConfigModel() 方法中,会使用页面的类名或者全路径类名,去初始化时解析的配置Map中进行id的匹配,如果匹配到说明页面需要测速,就会创建测速对象 PageObject 进行测速。

百度竞价悄然改版-程序员宅基地

文章浏览阅读42次。前段时间百度因为“魏则西事件”而被要求整改,百度的竞价排名也被推向了风口浪尖。最近有网友@闪电精灵SEO张扬爆料:百度竞价已改版,竞价显示4条,或许取消了右侧排名! 但是在经过风波之后,百度的确有了一些变化,最明显的变化就是对推广的网站的标注,之前的对百度推广只有推广两个字,现在是成了蓝色..._python 百度竞价排名

《剑指offer》学习笔记_面试题35_复杂链表的复制-程序员宅基地

文章浏览阅读334次。题目描述 请实现一个函数,复制一个复杂链表。在复杂链表中,每个节点除了一个next指针指向下一个节点,还有一个random指针指向链表中的任意节点或者为空。 思路 1.分治先复制当前的节点,然后递归的分别复制next或random。在复制next和random的过程中会重复复制相同节点,因此需要一个map来记录当前的复制情况。2.拆分法首先,将复制节点拼接到原始节点的...

嵌入式linux项目介绍与分享-基于 Linux 下 Socket 网络编程的局域网聊天室_linux项目局域网聊天室-程序员宅基地

文章浏览阅读1.4k次,点赞52次,收藏14次。本项目是基于 Linux 下 Socket 网络编程的局域网聊天室,实现了账号注册与登录、私聊消息、群发消息、发送离线消息、查看聊天记录、修改昵称密码等功能,并设置管 理员,实现将用户禁言、解禁、踢出聊天室等,采用多线程并发服务器模型处理多个客户端的同时连接和请求,服务器创建并管理用户数据、在线用户数据、聊天数据、离线消息数据等 SQlite 数据库,并提供后台服务,客户端通过 TCP 协议建立与服务器的稳定连接,并通过格式化输入输出实现与用户的交互。_linux项目局域网聊天室

推荐文章

热门文章

相关标签