经过6个月总共5次的讲解后,我终于理解了主席树并a了模板....
可持久化线段树,又名主席树。顾名思义,它支持对历史版本的询问,修改。
基础用法:静态查询区间第K大/小
洛谷P3834
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200005;
int a[N],b[N],T[N];//b是离散化数组,T[i]表示第i个版本的线段树根节点的编号,它储存了第i个版本线段树的全部信息
int L[N<<5],R[N<<5],sum[N<<5],tot;
int build(int l,int r){
int rt = ++tot;//当前节点编号,注意与普通线段树*2,*2+1编号规则不同,主席树是动态申请节点,所以需要记录左右儿子编号
sum[rt] = 0;//sum[rt]表示一个类似前缀和的东西...表示rt下面所有儿子中数的出现次数..比较抽象可以调试体会一下
if(l == r) return rt;
int mid = (l + r) >> 1;
L[rt] = build(l,mid);//L[rt]表示rt节点左儿子的编号,R[rt]同理
R[rt] = build(mid+1,r);
}
int insert(int pre,int l,int r,int x){//pre代表上一个版本同层的节点编号
int rt = ++tot;
L[rt] = L[pre];R[rt] = R[pre];//新开的节点与pre是同一层,这意味着储存的区间是完全相同的,在没有插入前需要继承pre所有信息
sum[rt] = sum[pre] + 1;//此时sum[rt]包含了新插入的值,所以一定比pre记录的sum值多1
if(l == r) return rt;
int mid = (l + r) >> 1;
if(x <= mid) L[rt] = insert(L[pre],l,mid,x);//x对应离散化后数组的值,意味着表示现在插入的数是原数组中第x小的,而
else R[rt] = insert(R[pre],mid + 1,r,x);
}
int query(int x,int y,int l,int r,int k){
if(l == r) return l;
int ans;
int temp = sum[L[y]] - sum[L[x]];//[l,r]中左区间一共有多少个出现过的数
int mid = (l + r) >> 1;
if(k <= temp) ans = query(L[x],L[y],l,mid,k);//如果左区间不少于k个,说明区间第k小一定在左区间内
else ans = query(R[x],R[y],mid + 1,r,k - temp);//反之,右区间还剩k-temp个,继续递归
return ans;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
b[i] = a[i];
}
sort(b+1,b+n+1);
int q = unique(b+1,b+1+n) - b - 1;
T[0] = build(1,q);
for(int i = 1;i <= n;i++){
int t = lower_bound(b+1,b+q+1,a[i]) - b;
T[i] = insert(T[i-1],1,q,t);//扫到一个就更新一个版本,表示[1,i]区间中的信息
}
int x,y,k;
for(int i = 1;i <= m;i++){
scanf("%d%d%d",&x,&y,&k);
int pos = query(T[x-1],T[y],1,q,k);//版本间有很多公共点,满足可减性,y版本减x-1版本就是[x,y]区间信息
printf("%d\n",b[pos]);
}
return 0;
}