概述
可持久化数据结构可以存储数据集在任意时间的历史状态。“可持久化”的基本思想是在每项操作结束后,仅创建数据结构中发生改变的部分的副本,不拷贝其他部分。这样一来,维护数据结构的时间复杂度没有增加,空间复杂度仅增长为与时间同级的规模。换言之,可持久化数据结构能够高效地记录一个数据结构的所有历史状态。
可持久化Trie
【实现过程】
- 设当前可持久化Trie的根节点为root,令p=root,i=0
- 建立一个新的节点,令root‘=q
- 若p!=0,则对于每种字符c,令trie[q,c]=trie[p,c]
- 建立一个新的节点q’,令trie[q,s]=q‘
- 令p=trie[p,s],q=trie[q,s],i=i+1
- 重复步骤3-5,直到i到达字符串末尾
【例题】最大异或和 luogu-p4735
给定一个非负整数序列{a},初始长度为N。
有M个操作,有以下两种操作类型:
A x
:添加操作,表示在序列末尾添加一个数x,序列的长度N+1。Q l r x
:询问操作,你需要找到一个位置p,满足l≤p≤r,使得: a[p]⊕a[p+1]⊕…⊕a[N]⊕x 最大,输出最大是多少。
【分析】
考虑异或前缀和。根据异或运算的性质:
$$
a[p]\oplus a[p+1]\oplus …\oplus a[N]\oplus x=s[p-1]\oplus s[N]\oplus x
$$
对于添加操作,序列s很容易维护。对于询问操作,问题变为:已知一个整数val=s[N] xor x,求一个位置p,满足l-1<=p<=r-1,使得s[p] xor val最大。显然可以将s数组插入可持久化Trie中,每次取出在l与r范围内的数据进行贪心(尽量往相反的节点走),从而求出答案。
【代码】
1 | #pragma GCC optimize(3)//QwQ |
可持久化数组
【例题】【模板】可持久化数组 luogu-p3919
如题,你需要维护这样的一个长度为 N 的数组,支持如下几种操作
- 在某个历史版本上修改某一个位置上的值
- 访问某个历史版本上的某一位置的值
此外,每进行一次操作(对于操作2,即为生成一个完全一样的版本,不作任何改动),就会生成一个新的版本。版本编号即为当前操作的编号(从1开始编号,版本0表示初始状态数组)
【分析】
在原数组上建立线段树,在叶子节点上记录原数组数值。执行完修改操作后,根据可持久化的思想,只需更改一条链上的节点信息;执行完访问操作后,则可以将目前操作的根节点指针指向被查询的历史状态根节点。
【代码】
1 | #include<bits/stdc++.h> |
可持久化值域线段树(主席树)
【例题】【模板】可持久化线段树 luogu-p3834
给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
【分析】
值域有负数出现,考虑离散化。假设离散化后的值域为[1,L]。
在值域上建立线段树,每个节点上存储该值域内有多少个数据。对线段树进行可持久化处理,与上一题可持久化数组实现方式类似。
在查询时,如果一个节点的左子节点上的cnt值小于等于正在查询的K,则问题转化为求左半区间内第K小值;否则,转化为求右半区间内第K-cnt小值。
查询时的操作类似于在值域上的二分,因此复杂度在log级别。
【代码】
1 | #include<bits/stdc++.h> |