可持久化线段树
前置知识
线段树、离散化、可持久化概念、二分
问题引入
这里我们就用最简单的静态求区间第k小数问题来引入,谷站链接:P3834 【模板】可持久化线段树 2。
这里简单描述一下题目:
有一组数量为$n(n\le2e5)$的数据(每个数$num\le1e9$),$m(m\le1e9)$次询问,每次询问该数据中编号为$[l,r]$区间中的第$k$小数,这里的编号从1开始,$k$也从1开始
思考过程
首先,我们可以发现这个问题是一个区间查询问题,可以想到要用线段树这种数据结构来维护,但如果简单地用线段树去维护原先的数组,“第$k$小数”这个信息并不能很好地维护,且不能通过子节点来推出父节点的信息。那我们可以转变一下思路,在这个问题中我们关注的并不是这个数本身的大小,而是这个数在某一区间当中的排名,所以只要维护区间上数的大小关系即可,那么通过离散化我们可以将答案限制在一个较小的范围内,显然,这样的情况是很适合二分的,并且我们可以注意到线段树本身便是对区间的一种二分处理。那么我们可以对线段树做一个改造,不去维护原来的数据,而是维护我们的答案范围,并同时记录每个区间上有多少个离散化后的读入数据。这样对于每一次询问,我们只需要从线段树的根节点开始,先看左儿子上拥有的数的个数(记为$sum$),如果大于等于$k$,那么我们的答案就必然在左儿子所维护的区间内部;而如果小于$k$,答案便在右儿子的区间内部,并且为右儿子区间上拥有的数中的第$k-sum$个数,如此递归查询,直达达到叶节点,便能确定我们的答案了。
然而如果只是这样的话,我们并不能保证$sum$中的每一个数的下标都在$[l,r]$之间,所以$sum$是大于等于真实值的,这便要求我们的数据结构可以维护每一次加入一个数前后的线段树上的节点信息情况,而这就是“可持久化”的用武之地了,这个数据结构也就是我们今天的主角——可持久化线段树
介绍
综上所述,可持久化线段树并不是普通的区间维护线段树,而是一颗权值线段树。可持久化这一概念就不多赘述了(可以参考链接中OI Wiki的描述),也就是对读入的每个数进行动态开点,先复制上一版本的信息,然后再新建一些点保留这次更新的信息,由于每次的都加入一个点,且树高为$\log_2n$,那么每次会更改不超过树高的点,单次开点的复杂度便是$O(\log n)$。如果对这个过程有不理解的话,我的建议是(1)在纸上画一次开点的过程,模拟一下;(2)看代码来协助理解。
这里加(盗)一张图,来便于理解。
代码(带注释)
1 |
|