主席树小结
前言
特别基础的就不说了,自己(Baidu)。
直接上套路好了。
(P.s):(YL)的主席树总结这里出门左转:戳我
一.树上路径第K大
考虑固定一个点为根,然后从上往下建值域主席树。
每个点(u)的主席树从它的父亲(fa_u)转移过来。
查询:
类似求(LCA)的方法,但注意要保留(LCA)这个点:
直接四个主席树加加减减即可。
实例题目:BZOJ2588 Count on a tree 、 SDOI2013 森林 。
二.子段、子区间第K小
把找第(K)(大/小)子区间转化为找第(K)(小/大)前缀。
不解释直接给题目。
实例题目:NOI2010 超级钢琴
三.局部修改(无后效性)操作
注意到主席树维护的是前缀。
所以有些修改利用这个性质可以暴力修改。
例题:G. Can Bash Save the Day?
题目大意:
给定一棵(n)个节点的树和一个排列A,有两种操作:
%%% $1 x (:交换)A_x , A_{x+1}$。
%%% $2 l r v (:求)sum_{i=l}^r dist(A_i , v)$。
强制在线。
首先求第二问的那个套路大家都会吧,不会见HNOI2015 开店这道题。
注意到主席树是维护的前缀,
交换(A_x),(A_{x+1})其实只有第(x)棵主席树(Tree_x)变化了。
重新(Copy)一下(root_{x-1}),然后暴力重建即可。 代码戳我。
四.有关区间不重复个数的问题
关于区间不重复数的个数可以用主席树做。
不要维护值域,而应该要维护位置。
例题:
一共(Q)次询问,每次询问([l,r])中不重复的数的个数。
从前往后一个一个数加。
如果这个数在之前出现了,并且出现位置为(p),那么在那个位置-1。
然后在当前位置+1即可。
很好理解,出现在后面肯定是更优秀的!
查询([l,r])时,就是查询(Tree_r)中([l,r])区间内的和(只查一棵主席树!)。
实例题目:SP3267 DQUERY D-query、CF813E Army Creation
其中第二题的代码见这里:戳我
五.二分后的0/1转换套路
0/1的套路同HEOI2016 排序。
二分一个答案,然后把大于它的设为1,小于它的设为0或-1。
然后各种线段树搞事情(如最大子段和等)来进行(check)。
主席树的作用主要是优化转化0/1这个过程。
把所有数按照大小(sort)一遍(记录原位置)。
然后 从小到大/从大到小 依次加入数字,每次把对应位置的数变为-1/0/1。
具体的 初始化 和 加入顺序 依题目而定,需要仔细分析。
这个强烈推荐去看一下这两个题目:
实例题目:CF484E Sign on Fence , 国家集训队 middle 。
这两题的代码看这里:戳我 ;
六.主席树维护一次函数
例题
(n*m)的矩阵,初始权值都为(0) 。
首先会有(d)个修改操作形如:
([ (x_1,y_1,x_2,y_2)),(V ]),把这个范围内的方格的权值+(V)。
在修改结束后,会有(Q)个询问,
每次询问一个矩阵((x_1,y_1,x_2,y_2))内的权值和。
数据范围:(n,mleq 10^8) ; (dleq 4*10^4) ; (Qleq 10^5) ; 答案对(2^{64})取模。
首先取模(2^{64})直接开(unsigned long long)自然溢出即可。
主席树维护一次函数.......
首先对每一个(y)坐标建立一棵主席树,每一棵主席树下标维护(x)坐标。
一次函数是什么意思呢?
显然,每一列((x)坐标)的权值前缀和是一个关于(y)坐标的分段一次函数。
一次函数具有可加性。
所以假设我们可以维护这个一次函数,
那么每次查询的答案就是(sum_{x=x_1}^{x_2} [ f(y2)_x - f(y_1-1)_x ])。
把(y)坐标离散化,(x)坐标直接动态开点。
考虑修改,设修改的(y)坐标为([l,r]),所加权值为(V)。
对于斜率的拔高,我们可以列出方程:
(Delta k*l + Delta b = V)
(Delta k*r + Delta b = V(r-l+1))
就是对应这两个点增长了多少,解得:(Delta k = V , Delta b = V(1-l))
所以对应的在第(l)棵主席树上加上(Delta k)、(Delta b)。
在(r+1)的位置上斜率恢复正常,截距增高,对应到 这个点的前缀和增长量 有:
((Delta k + k')(r+1) + (Delta b + b') = (r-l+1)V)
((Delta k + k') = 0)
解得:(k' = -Delta k , b' = -rV)。
在第(r+1)棵线段树上加上(k')、(b'),使得斜率恢复正常。
查询的时候直接把(y_1,y_2)带入对应的主席树中算出前缀和,然后作差即可。
实现代码:戳我。