主席树学习笔记
简介:
主席树是一种可持久化数据结构,全称为可持久化权值线段树,支持查询历史版本内信息,单点修改同时新建版本等操作,也是一种函数式线段树, 可支持完全持久化。
原理:
首先考虑朴素的做法,每次新建版本时都 copy 一份,并且在这个 copy 下来的版本上面进行操作。这种做法空间复杂度是很大的。
观察到我们每次插入一个点后,至多有 \(O(\log n)\) 个节点的信息发生改变。
我们可以沿用动态开点的思想,每次修改时对于新版本的根节点以及从根节点到需要修改的节点的位置中的每一个点都新开一个节点,并同时将新节点的对应的旧节点的信息 copy 过来,并在这个基础上修改。
这样说有点抽象,可以根据下面的图理解。
(其中红色节点为新开的节点)
例题:【模板】可持久化线段树 2
考虑对于每个 \(a_i\) 在主席树上都建一个新的版本。
那么我们该怎么回答询问呢?
由于主席树关于函数的美妙性质,主席树版本之间是可减的。具体的,我们对于每个节点维护一个 \(sum\) 记录该节点对应的区间有多少个数。对于询问 \([l,r]\),(先咕了)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
struct Persist_tree{
#define mid (l + r >> 1)
struct node{
int sum, ls, rs;
}hjt[N << 5];
int cnt, root[N];
void upd(int l, int r, int pre, int& now, int x){
hjt[++cnt] = hjt[pre]; now = cnt;
hjt[now].sum++;
if(l == r) return;
if(x <= mid) upd(l, mid, hjt[pre].ls, hjt[now].ls, x);
else upd(mid + 1, r, hjt[pre].rs, hjt[now].rs, x);
}
int querykth(int l, int r, int Lo, int Ro, int k){
if(l == r)return l;
int rank = hjt[hjt[Ro].ls].sum - hjt[hjt[Lo].ls].sum;
if(k <= rank) return querykth(l, mid, hjt[Lo].ls, hjt[Ro].ls, k);
else return querykth(mid + 1, r, hjt[Lo].rs, hjt[Ro].rs, k - rank);
}
}tr;
int n, m, a[N], rk[N];
int getid(int val){return lower_bound(rk + 1, rk + m + 1, val) - rk;}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> n >> T;
for(int i = 1; i <= n; i++)cin >> a[i], rk[i] = a[i];
sort(rk + 1, rk + n + 1);
m = unique(rk + 1, rk + n + 1) - (rk + 1);
for(int i = 1; i <= n; i++)tr.upd(1, m, tr.root[i - 1], tr.root[i], getid(a[i]));
while(T--){
int l, r, k;
cin >> l >> r >> k;
cout << rk[tr.querykth(1, m, tr.root[l - 1], tr.root[r], k)] << "\n";
}
return 0;
}