可持久化数据结构·主席树(静态)
emmmmm在6月9日上午的省选培训考试中,选择用最后一个小时学主席树,因为我除了暴力啥都不会太爱好学习新知识了。
那么现在介绍可持久化的定义(个人理解):
一个可持久化算法不一定非要是在线算法,但就算是在线算法也可也查询之前操作时的状态。
\(qwq\)好像\(\rm{starfish}\).
那么好像很显然的一点是,如果我们要做到可持久化,就必须牺牲大量的空间,不然历史就会被忘却。
那么对于线段树而言,为了忘却的纪念可持久化,就同样需要建立一大堆线段树因为你如果要查询每一个历史版本的线段树,就必须要对于每一次\(modify\)之后的线段树状态掌握得清清楚楚qwq。
譬如说我一棵空树,不断向内插入点值的\(rank\),然后不断迭代更新,那么每两次更新之间就是两种不同的状态,所以要分别记录。
那么盲目建树的空间浪费就会很大,空间复杂度大约在\(O(4n^2)\)左右。然后就原地爆炸
诶,我们会发现每一次更新,事实上就是添一条新的链,所以我们不妨每一次不重新建树,而是选择多向根节点上挂一条链,长度为\(log_2n\)(显然)。
那么空间复杂度就变成了\(O(n \log n)\)(忽略常数2,渐进意义下没用),可以接受.jpeg。
所以事实上……静态的主席树跟普通的权值线段树没啥区别……就是空间大了点。
\(emmm\)事实上应该算是“名次树”之类的…
看一道乍一看跟可持久化没有关系的题:
\(\color{red}{\mathcal{Description}}\)
给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
\(\color{red}{\mathcal{Solution}}\)
呐,这个题唯一用到可持久化的地方就是——找区间。
我们如何重现区间\(l\)$r$呢?利用主席树的函数性(单调性$&$可加性),我们会发现,之后的树永远比之前的树有效节点个数要多,所以就类似前缀和作差求得$l$\(r\)区间内数的个数。类似的,我们让每个节点存储其区间内数的个数(可重集),那么之后就类似于在一棵树里找第\(k\)小的值,我们会发现如果是在树里找第k小的复杂度上界为\(O(\log n)\),,而在区间里直接找的话时间复杂度为\(O(n \log n)\),优化了很多。
呐,第\(k\)小值怎么找啊……\(Splay\)怎么找,主席树就怎么找呗(雾
好吧,其实权值线段树,包括此处的主席树,和二叉搜索树相比有相似的性质——节点的秩单调。放在主席树这儿,就是左儿子(们)的\(rank\)一定比右儿子(们)的小。所以查询的时候大致是这样:
int query(int pre, int aft, int l, int r, int k){
if(l >= r)return l ;
int now = sum[Ls[aft]] - sum[Ls[pre]] ;//本区间内的数的个数
int mid =(l + r) >> 1 ;
if(k <= now)return query(Ls[pre], Ls[aft], l, mid, k) ;
else return query(Rs[pre], Rs[aft], mid + 1, r, k - now) ;
}
代码里的\(Ls\)存左儿子,\(Rs\)存右儿子,\(T\)存根, \(cnt\) 为节点编号(两两不同(废话\(qwq\)))。
因为我们的\(query\)和\(update\)等一众函数是查询位置的,所以一定要离散化啊qwq(刚学会用\(\rm{unique}\))
#include<algorithm>
#include<iostream>
#include<cstdio>
#define MAXN 120010
using namespace std;
int cnt, n, m ,len, i, t, a, b, c , base[MAXN], at[MAXN];
int sum[MAXN<<5], Ls[MAXN << 5], Rs[MAXN << 5], T[MAXN << 5] ;
int build(int l, int r){
int p = ++ cnt ;
sum[p] = 0 ;
if (l < r){
int mid = (l + r) >> 1 ;
Ls[p] = build(l, mid) ;
Rs[p] = build(mid + 1, r) ;
}
return p ;
}
int update(int pre, int l, int r, int k){
int p = ++ cnt ;
Ls[p] = Ls[pre] ;
Rs[p] = Rs[pre] ;
sum[p] = sum[pre] + 1 ;
int mid = (l + r) >> 1 ;
if (l < r){
if(k <= mid) Ls[p] = update(Ls[pre], l, mid, k) ;
else Rs[p] = update(Rs[pre], mid + 1, r, k) ;
}
return p ;
}
int query(int pre, int aft, int l, int r, int k){
if(l >= r)return l ;
int now = sum[Ls[aft]] - sum[Ls[pre]] ;
int mid =(l + r) >> 1 ;
if(k <= now)return query(Ls[pre], Ls[aft], l, mid, k) ;
else return query(Rs[pre], Rs[aft], mid + 1, r, k - now) ;
}
int main(){
cin >> n >> m ;
for(i = 1; i <= n; i ++){
cin >> base[i] ;
at[i] = base[i] ;
}
sort(at + 1, at + n + 1) ;
len = unique(at + 1, at + n +1) - (at + 1) ;
T[0] = build(1, len) ;
for(i = 1; i <= n; i ++){
t = lower_bound(at + 1, at + n + 1, base[i]) - at;
T[i] = update(T[i - 1], 1, len, t);
}
for(i = 1; i <= m; i ++){
cin >> a >> b >> c ;
cout << at[query(T[a-1], T[b], 1, n, c)] << endl ;
}
return 0 ;
}