对主席树的理解以及使用
引入
一个长度为$n$的数组,有$m$次查询,每次查询区间$[l,r]$内第$k$小的元素。
如果使用暴力,肯定不可以
使用线段树?可是我只会查询区间最值啊。
那么我们把问题再次简化一下,查询$[1,n]$第$k$小的元素,要求使用线段树来实现。
权值线段树
为了解决这个问题,我们引入一个名词:权值线段树。那么权值线段树是如何解决上面那个问题的呢?
首先,我们对数组进行离散化处理,离散成为$[1,n]$,然后我们建一颗线段树,线段树的节点存放的即为对应区间的数的个数。
比如数组$a={3,3,2,2}$,经过离散化后变为$2,2,1,1$。
对应的线段树即为:
建好线段树之后我们如何求解第$k$小元素呢?我们从根节点出发,看下它的左儿子的元素个数是否超过了$k$,如果超过了$k$,那么第$k$小一定是左儿子的第$k$小,我们直接去访问左儿子,否则,假设左儿子的节点为$num$,那么第$k$小一定是右儿子的第$k-num$小,我们去访问右儿子,直到递归终止,我们便找到了第$k$小元素。
主席树
当我们解决了上一个问题,我们这样考虑:
每输入一个数字$a_i$,就建一棵$[1,i]$的权值线段树,那么如果要查询$[l,r]$的区间第$k$小,直接让这两棵权值线段树做差,然后进行我们上面设计的算法,问题不久迎刃而解了吗?
但是,每建一棵树,这样$n$棵树的空间会达到$O(n^2)$的级别,空间是无法承受的。我们这样想,假设你输入了$a_i$,并且你已经建好了$a_{i-1}$的线段树,是不是$a_i$和$a_{i-1}$的线段树只会有$log$级别的点是不同的,剩下的大部分都是完全一致的。利用这个性质,我们不再开辟新的线段树,而是先把$a_i$会改变掉的节点复制一份,然后对复制的节点进行修改,连接到上次构建好的线段树上,这样我们只用了$log$的空间。最终我们构造的这棵树就叫主席树(其实已经不是一棵树了)。点的个数最多为$O(nlog(n))$。
建立过程
对于数组$a:3,3,2,2$建立主席树:
第一步:离散化为$2,2,1,1$
第二步:输入$2$,构造权值线段树
第三步:输入2
第四步:输入1
第五步:输入1
这样我们就构造了一个主席树(有点丑),然后对于要查询的区间$[l,r]$,我们只需要从他们各自的”根”出发,递归做差寻找第$k$大即可。图中四个根分别为$1,4,7,10$。
代码:
1 |
|