可持久化线段树/主席树 总结

前言

可持久化数据结构其大多数有两种用法,但是其本质都是维护了一个“前缀数据结构”。

第一种用法是对于可以差分的信息(不只是序列上的差分,也可以是树上差分),我们就可以直接通过“前缀和”相减来得到对应区间的信息,从而维护。

第二种用法比较纯粹,就是在我们需要访问这个数据结构每一个历史版本的时候,我们能够直接取出来使用了。

另外,主席树这个名称特指“可持久化值域线段树”,之后就不再提了。

正文

基础题

最常规的题目,大多是模板。

例题

BSOJ5526&LOJ519 数学上来先打表

5526【Loj519】数学上来先打表

操作树+值域分块+并查集,然后就比较容易了。

具体不再赘述,请看题解。

BSOJ6733【模板】可持久化数组

【模板】可持久化数组

模板题,使用可持久化线段树维护即可。

BSOJ6735【模板】可持久化栈

BSOJ6735【模板】可持久化栈

似乎卡了空间,于是直接操作树建出来做。

BSOJ6734 【模板】可持久化并查集

BSOJ6734 【模板】可持久化并查集

可持久化线段树维护 (fa) 数组,并查集按秩合并即可。

BSOJ6729 【模板】区间第k小值(主席树)

BSOJ6729 【模板】区间第k小值

模板题,做法思路就是主席树维护每一个前缀下标构成的线段树,然后发现每个数的个数可以差分,于是可以差分来实现区间询问,然后剩下的就是线段树上二分了。

SP11470 TTM - To the moon

SP11470 TTM - To the moon

可持久化线段树区间修改模板。

注意可持久化线段树尽量还是需要标记永久化,因为如果是标记下传的话,每次下传的时候还要多复制一下所有的标记,空间消耗极大,所以一般使用标记永久化,所以注意写法。

P1903 [国家集训队]数颜色 / 维护队列

P1903 [国家集训队]数颜色 / 维护队列

讲过一万次的题目,区间数颜色问题。

我们之前使用的是离线后树状数组的做法,这里介绍在线的主席树。

还是对于每一个点维护前驱的位置,然后询问就是在两个树上差分,询问哪些下标的权值在区间([1,l-1])当中即可。

常规题目

稍微常规一点的题目,没有特别 (trick)

BSOJ3876【BZOJ3207】花神的嘲讽计划Ⅰ

BSOJ3876【BZOJ3207】花神的嘲讽计划Ⅰ

哈希过后发现本质就是查询一个区间内是否含有某个数,直接主席树解决。

BSOJ3401【模拟试题】建筑

BSOJ3401【模拟试题】建筑

本质就是询问一个下标区间([l,r]),且权值在值域区间 ([p,q]) 的第 (k) 大的数。

可以直接主席树上二分解决。

BSOJ2895【BZOJ2223】PATULJCI

BSOJ2895【BZOJ2223】PATULJCI

直接主席树上二分即可,因为是一半,所以必然只会有一边区间满足,要不然就是两边都不满足。

BSOJ1583【BZOJ3524】Couriers

同上。

BSOJ2251【BZOJ3744】Gty的妹子序列

BSOJ2251【BZOJ3744】Gty的妹子序列

询问区间逆序对,强制在线。

可以先离散化,然后分块维护整块,对于散块主席树维护。

存在在线的分块(O(nsqrt{n}))做法,和同样复杂度的离线莫队做法。

CF853C Boredom

CF853C Boredom

稍微容斥一下就会变成一个二维数点问题,可以直接主席树。

P3168 [CQOI2015]任务查询系统

P3168 [CQOI2015]任务查询系统

主席树模板,其实就是对于每一个下标维护一下值域线段树,然后线段树上二分查询全局第 (k) 小即可。

BSOJ2781【模拟试题】区间第k大(版本2)

BSOJ2781【模拟试题】区间第k大(版本2)

单点修改区间第 (k) 大,主席树套树状数组维护。

不想写了,离线整体二分做的。

P3332 [ZJOI2013]K大数查询

P3332 [ZJOI2013]K大数查询

可以整体二分+线段树,也可以主席树+树状数组,相对于常规的就是多了一个区间修改而已。

经典题/Trick题/妙题

P2839&BSOJ3867 [国家集训队]middle

P2839&BSOJ3867 [国家集训队]middle

关于中位数的常用套路:二分中位数,然后小于的变成 (-1) ,剩下的变成 (1) ,然后数和即可,和大于0证明当前数小了,和小于0证明当前数大了。

但是在这道题当中,相当于要让中位数尽可能大,于是我们就要尽可能多的取到 (1) ,那么我们这道题因为中间的区间是固定的,那么变化的就只有前后两边。

那么我们考虑线段树维护,但是这样的话我们每次询问都要二分再维护一次,这样很慢。

然后我们发现其实 (mid) 的变化量如果只有 (1) 的话,只有很少位置会改变,于是考虑可持久化线段树,对于每一个 (mid) 值维护一个线段树。

然后直接去线段树上询问即可。

太妙了,这道题可以说是完美应用到了可持久化的性质。

P4602&BSOJ5586 [CTSC2018]混合果汁

P4602&BSOJ5586 [CTSC2018]混合果汁

和上一题有异曲同工之妙,这道题可以离线,那么也可以使用整体二分来做,这里讲主席树做法。

显然答案具有单调性,于是就可以二分,那么我们直接考虑对每一个值建立线段树,但是开不下,所以考虑主席树,然后最后求答案就在每一个询问上二分然后线段树上二分即可。

同样是巧妙地应用了主席树,感觉这类二分题都能这样做?

P4587&BSOJ6169 [FJOI2016]神秘数

P4587 [FJOI2016]神秘数

这题比较 (trick),首先我们考虑怎么来求神秘数,发现我们对于每一个数可以这样来更新:

对于 (x) ,如果我们可以得到前 (nowsumge x),那么我们就可以得到:前(nowsum o nowsum+x)

那么我们就可以通过不停地维护 (nowsum) 直到不存在这样的 (x) 为止。

发现这样需要建立值域线段树,然后这里又是区间询问,所以主席树即可。

P3293 [SCOI2016]美味

P3293 [SCOI2016]美味

按位贪心,可以可持久化(01trie)或者主席树,这里说主席树做法。

对于最大异或和的按位贪心,其实我们除了一般的在 (01trie) 上二分,还有一个就是把当前位对应的值域区间直接写出来,然后再直接维护整个值域。

也就是说,假设我们要求的是这里的 (b_i xor (a_j+x_i))

那么相当于如果 (b) 的这一位是 (1)(a_j) 就落在 ([now+(1<<k)-x,now+(1<<(k+1))-1-x]) 这个区间当中会让这一位成为 (1) ,所以我们就直接在值域线段树上查询这一段是否存在数即可。

然后这样一位一位累加下去即可。

因为这里是区间查询,所以我们需要把我们的值域线段树可持久化,也就是主席树了。

这道题对于异或和的处理比较新也比较 (trick)

P3241 [HNOI2015]开店

P3241 [HNOI2015]开店

可以点分树,这里讲可持久化线段树做法。

先这样转化一下:

然后现在重点是求点权在一段区间内的点和 (u) 之间的(LCA)的所有(dis)和。

于是我们按照点权从小到大来建立可持久化线段树,每一个都维护前缀,也就是说我们如果每加进来一个点,那么我们就在这个点到根的路径上所有点加上(1)

然后询问就是直接询问当前点到根的路径上的区间和,然后因为是区间询问,所以直接用 ([1,r]) 的答案减去 ([1,l-1]) 的答案即可。

这道题是前缀数据结构维护树上路径的 (trick)

BSOJ1781【BZOJ3514】GERALD07加强版

BSOJ1781【BZOJ3514】GERALD07加强版

似乎可以直接线段树分治,但是这里强制在线,于是考虑在线做法。

我们考虑 (lCT) 来求出每一条边加入的时候,如果当前构成了环,那么我们就删掉环上最早出现的边,并称这条删除的边是可以“替代”当前边的,就是把当前边 (pre[i]) 设为 (tmp) (删除的边的编号)。

然后我们发现问题变成了:询问一段区间内小于某个数的个数,直接主席树即可。

这道题重点在于 (LCT) 的那一步转化,或许这题更应该是个 (LCT) 题(?)。

P4559 [JSOI2018]列队

P4559 [JSOI2018]列队

容易发现这题如果我们保持学生的相对顺序不变,答案肯定是不劣的,所以我们就让学生保持排队后的相对顺序不变即可。

那么肯定会出现一个分界点,这个点左边的学生往右跑,右边的往左跑。

BSOJ1783【BZOJ3658】Jabberwocky

BSOJ1783【BZOJ3658】Jabberwocky

发现其实最优的策略就是只有一个颜色不包含。

于是可以考虑枚举这个颜色,然后再发现这个线一定落在点上最优,于是考虑枚举每一个点。

但是我们还要保证选的区间不包含第二个这种颜色的点,才能保证不包含这个颜色。

于是我们可以这样来决定左右边界,也就是如果现在我们要选上面,那么我们当前点对应可以选的 (x) 坐标区间就在 ([pre_i+1,suf_i-1]) ,且这两个数组只是对于这个点及(y)坐标大于等于这个点的那些点来说的,因为这样才可以保证这个颜色完全没有。

然后我们就是一个二维数点问题了,可以可持久化线段树来做。

BSOJ5381【BZOJ3956】Count

BSOJ5381【BZOJ3956】Count

我们发现,可以利用单调栈,对于每一个点作为 (i) ,求出对应的 (j) 的可行区间。

于是这就是询问一段区间内,且权值满足在一段区间的数的个数,那么可持久化线段树即可。

这种对于每一个左端点,求出右端点所在区间,然后可持久化线段树询问的技巧也很好用。

P3722 [AH2017/HNOI2017]影魔

P3722 [AH2017/HNOI2017]影魔

和上一题比较类似,就是单调栈求出一个区间对另一个区间的贡献,一个点的贡献,然后主席树维护即可。

这道题可以离线,于是也可以离线后树状数组。

CF464E The Classic Problem

CF464E The Classic Problem

首先我们发现肯定不能直接维护因为会炸,但是这里的数以二进制给出,容易想到维护二进制串来表示我们的数。

但是这样做的话,最短路会用到的比较和做加法效率都很低了,是 (O(n)) 单次的。

于是考虑使用数据结构来维护这个二进制串。

发现对于动态开点线段树,其实就很好来维护这个比较,直接在线段树上二分即可。

但是加法的话就相当于要复制这个线段树,然后再给某一位加一。

等等,复制线段树?

于是我们可以想到可持久化线段树,并且这里的单次修改很少。

然后我们考虑怎么来做加法。

我们先线段树上二分找到第一个零的位置,然后把这个点单点修改成(1),再把前面那一段区间设置成零即可。

后面这个区间修改我们可以使用一个技巧。这里有两种实现方法,第一种是建立一个全0的树,然后直接把儿子指过去就行了,第二个办法是直接设为空节点,默认就是 (0) 了。

这样做复杂度是显然正确的。

然后就是最短路模板了,(Dijkstra) 即可,时间复杂度 (O(nlog^2n))

BSOJ3865 BZOJ2588 SPOJ10628 P2633 Count on a tree

BSOJ3865 BZOJ2588 SPOJ10628 P2633 Count on a tree

套路题,就是维护树上前缀主席树,然后询问就是树上差分的思路在线段树上二分即可。

BSOJ3405【模拟试题】路径第k大

BSOJ3405【模拟试题】路径第k大

今有一n点之无根树,点上有权,试问路径之第k大点几何?

询问路径第 (k) 大,可以直接建立前缀主席树,然后树上差分在线段树上二分第 (k) 大即可。

P4216 [SCOI2015]情报传递

P4216 [SCOI2015]情报传递

单点修改,询问路径上权值小于某个数的个数。

两个做法,一是先读完直接改完所有点权,然后做静态的差分+主席树即可维护,复杂度 (O(nlogn)) ,另外一种是,就是树剖+主席树,这个做法就比较显然,但是劣一些,是(O(nlog^2n))的。

P3302 [SDOI2013]森林

P3302 [SDOI2013]森林

发现如果只有第一个操作那么就是可持久化线段树,上面已经提到过了。

现在有了第二个操作,其实看到“合并”就启示我们想启发式合并了,于是我们直接启发式合并,然后暴力更新小的那个树的整个子树的信息即可。

SP1487 PT07J - Query on a tree III

SP1487 PT07J - Query on a tree III

查询子树第 (k) 大,没什么意思,直接 (dfn) 序就是区间第 (k) 大了。

P1552 [APIO2012]派遣

P1552 [APIO2012]派遣

左偏树题,放到这里就没做了。

CF893F Subtree Minimum Query

CF893F Subtree Minimum Query

换成离线的话就是经典的(dsu on tree)了,可惜换不得。

那么在线其实我们发现这里前缀构造还是(dfn)序构造主席树都不行了,那么怎么办呢?

我们发现其实一个点子树内且距离它深度不超过 (k) 其实可以转化成两个限制,一是(dfn)序在一段区间内,二是深度在一段区间内。

两个限制,然后询问最小值,直接可持久化线段树维护区间最小值即可,这里按照每一个深度建立或者按照(dfn)序建立可持久化线段树都可以。

BSOJ4205【BZOJ4771】七彩树

BSOJ4205【BZOJ4771】七彩树

可持久化线段树合并,具体见题解,懒得讲。

CF226E Noble Knight's Path & BSOJ4102【2014NOI模拟5】旅行

CF226E Noble Knight's Path & BSOJ4102【2014NOI模拟5】旅行

询问路径上权值在一段区间内,且位于路径上第 (k) 个满足上述条件的点的编号。

先树剖,然后不断地跳,先从(u o lca) ,然后(lca o v) ,直到当前重链满足要求的点的个数比当前的 (k) 要大,然后在这条重链上二分即可,注意方向。

思路比较简单,实现比较麻烦,看着都不想写。

P4175 [CTSC2008]网络管理

P4175 [CTSC2008]网络管理

树上带修路径第 (k) 大。

可以(dfn)序直接转化成序列问题,主席树套树状数组解决,复杂度(nlog^3n)

也可以直接主席树套树状数组,复杂度(nlog^2n),具体见题解第一篇。

然后也可以整体二分和树上带修莫队之类的,暂不赘述。

BSOJ4445【FJMTC2015#4】神牛的养成计划

BSOJ4445【FJMTC2015#4】神牛的养成计划

这题可以用两个 (trie) 直接把问题转化成:询问((u,v)):有多少个编号满足在第一棵树(u)的子树内,在第二棵树(v)的子树内。

两维限制,很明显是一个二维数点,可持久化线段树维护即可。

原文地址:https://www.cnblogs.com/Akmaey/p/14901186.html