前置知识
树状数组,线段树 (本文不讲)
权值线段树,树上二分,动态开点线段树(下文会讲)
前置知识讲解
权值线段树
虽然叫线段树,但事实上我们一般用树状数组去写权值线段树
并非像线段树那样维护数组下标区间 [ l , r ] [l, r] [l,r]中的信息,而是以权值为下标,维护权值为 [ l , r ] [l, r] [l,r]的数量
一个简单的例子:询问数组中有多少满足 5 ≤ x ≤ 8 5 \le x \le 8 5≤x≤8的权值 x x x
首先我们考虑如何维护权值线段树,对于第 i i i个元素 a i a_i ai,我们应该 b i t . m o d i f y ( a i , 1 ) bit.modify(a_i, 1) bit.modify(ai,1),表示权值为 a i a_i ai的数量 + 1 +1 +1
考虑查询,树状数组是类似于前缀和的思想, b i t . q u e r y ( x ) bit.query(x) bit.query(x)表示权值最多为 x x x的数量,那么就是 b i t . q u e r y ( 8 ) − b i t . q u e r y ( 4 ) bit.query(8) - bit.query(4) bit.query(8)−bit.query(4),值最多为 8 8 8的数量减去值最多为 4 4 4的数量,那么我们就得到了权值处于区间 [ 5 , 8 ] [5,8] [5,8]的数量
我们可以画个图简单的理解下:

在这个图中,表示数组有两个权值为一的元素,一个权值为二的元素,零个权值为四的元素,然后我们利用树状数组查询 b i t . q u e r y ( 4 ) bit.query(4) bit.query(4)相当于把所有小于等于 4 4 4的值的数量都求出来了,即 b i t . q u e r y ( 4 ) = 2 + 1 + 2 + 0 = 5 bit.query(4) = 2 + 1 + 2 + 0 = 5 bit.query(4)=2+1+2+0=5
注:下文的主席树还是以用线段树去实现权值线段树,这里主要是讲思想,所以用了树状数组
树上二分
学会了权值线段树后,我们考虑求全局第 k k k小,我们二分一个答案 m i d mid mid,然后计算权值处于区间 [ l , m i d ] [l, mid] [l,mid]的数量 c n t cnt cnt。
如果 c n t < k cnt < k cnt<k的话,那么显然答案不会在 [ l , m i d ] [l, mid] [l,mid]区间上,所以我们要判断区间 [ m i d + 1 , r ] [mid + 1, r] [mid+1,r]中哪个数有可能是答案,此外,因为 [ l , m i d ] [l, mid] [l,mid]都不是答案,所以 k k k要减去这些数量,即 k = k − c n t k = k - cnt k=k−cnt
如果 c n t ≥ k cnt \ge k cnt≥k的话,说明答案在 [ l , m i d ] [l, mid] [l,mid]区间内,那么我们继续迭代判断即可。
我们可以手动模拟一下:

例如我们在这个权值线段树上求全局第 3 3 3大
一开始 l = 1 , r = 6 , m i d = l + r 2 = 3 l = 1, r = 6, mid = \frac {l + r} 2 = 3 l=1,r=6,mid

最低0.47元/天 解锁文章
483

被折叠的 条评论
为什么被折叠?



