主席树(可持久化权值线段树)
这是一种用于处理一段数(e.g.1,2,3,4,5)在一个序列的某个区间[l,r]上出现几次/区间k小/查询历史版本的数据结构
前言:权值线段树
权值线段树可以维护某个区间内数组元素出现的次数。
1.主席树是什么?
对于一颗权值线段树,我们要往里面添加n个数字,我们知道,这很容易实现,只需要一个for循环,然后调用n次modify函数即可,但是如果需要一个记忆化的过程(可持久化),即我们每添加一个元素都需要记住这个权值线段树的状态,以便于我们对历史版本进行操作,那么如果只使用权值线段树,则我们需要n个权值线段树同时保存状态,这样做空间一定会爆炸,主席树则是解决这一问题的方法。
首先观察,每次更改(添加)一个数,线段树的变化
//图例
可以发现,对于每次添加,只会有一条以根节点为起点,代表被修改数字的叶子节点为终点的链有改动(长度为log n)因此,我们可以每次需改新建一条链,并将其与原树的某节点相连,形成新版本的线段树
时间复杂度O(NlogN)空间复杂度O(N+NlogN)
2.主席树实现
(1)建树先准备struct
与一般线段树不同,主席树每个节点不仅要记录权值还要记录左/右儿子的编号(因为每次要新建一条链)
用结构体存(数组要开logN倍以上)
(2)开始建树build
递归左右儿子,并记录编号
(3)要支持修改(添加一个数)modify
像线段树一样递归,每递归到一个节点,意味着它需要更新,复制一个新节点,向下递归,由于每个节点只有一个儿子与目标节点相交,所以新节点有一个儿子为原树上的节点,一个节点为新节点,向下递归并记录新儿子编号
(4)要支持查询 check
同线段树一样
3.典题精讲
静态区间第k小
建一个空树
对于每个\(a_i\)(小至大)进行一次修改操作形成n棵线段树
查询[l,r]区间第k小ans
已知ans属于[1,maxn]即根节点范围
令check(i,j)为已i为根(只含区间\(a_1~a_i\)的数)j号节点范围中数的数量
发明者的原话:“对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的”
可以加减的理由:主席树的每个节点保存的是一颗线段树,维护的区间信息,结构相同,因此具有可加减性
详细查询过程: 原始区间: [1,2,3] 查询 1到3 里的第二小的值(应该为2)
我们查询 l=1 r=3 k=2 因此这样查询:check(r,r) - query(l-1,l-1)(sum[r]-sum[l-1])
首先进入query函数,当前为12编号根节点,前一个为1编号根节点。对于编号12的根节点的左孩子编号为10,编号为1的根节点左孩子是2,让sum[10]-sum[2],sum记录的是此节点的val,相减得2,因为k=2,所以进入左子树递归。
当前为10编号根节点,前一个为2编号根节点。对于编号10的根节点的左孩子编号为8,编号为2的根节点左孩子是3,让d=(sum[8]-sum[3]),sum记录的是此节点的val,相减得1,因为k=2,所以进入右子树递递归,同时 k - d
当前为11编号根节点,前一个为4编号根节点。此时已经到达了叶子节点,返回 pl或者pr,得结果为 2(此时pl== pr==2,达到编号为4的叶子节点)
include<bits/stdc++.h>
using namespace std;
int a[200010],c[200010]; //c为离散化
int root[200010],top,n,m;
struct node{
int ls,rs,val;
}tree[40*200010];
int build(int cur,int l,int r){
cur=++top;
tree[cur].val=0;
if(l==r) return cur;
int mid=(l+r)>>1;
tree[cur].ls=build(cur,l,mid);
tree[cur].rs=build(cur,mid+1,r);
// cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
return cur;
}
int clone(int pre){
++top;
tree[top].ls=tree[pre].ls;
tree[top].rs=tree[pre].rs;
tree[top].val=tree[pre].val+1;
return top;
}
int modify(int pre,int x,int l,int r){
int cur=clone(pre);
if(l==r) return cur;
int mid=(l+r)>>1;
if(x<=mid){
tree[cur].ls=modify(tree[cur].ls,x,l,mid);
}
else{
tree[cur].rs=modify(tree[cur].rs,x,mid+1,r);
}
// cout<<cur<<' '<<l<<' '<<r<<" "<<tree[cur].val<<" "<<tree[cur].ls<<" "<<tree[cur].rs<<endl;
return cur;
}
int check(int cur,int pre,int l,int r,int x){
int L1=tree[cur].ls;
int L2=tree[pre].ls;
// cout<<cur<<' '<<pre<<" "<<L1<<" "<<L2<<endl;
if(l==r) return l;
int mid=(l+r)>>1;
int num=tree[L1].val-tree[L2].val;
if(num>=x){
return check(tree[cur].ls,tree[pre].ls,l,mid,x);
}
else{
return check(tree[cur].rs,tree[pre].rs,mid+1,r,x-num); //x-num
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],c[i]=a[i];
sort(c+1,c+n+1);
int cnt=unique(c+1,c+n+1)-c-1;
root[0]=build(0,1,cnt);
// cout<<endl;
// cout<<cnt;
for(int i=1;i<=n;i++){
int num=lower_bound(c+1,c+cnt+1,a[i])-c;
root[i]=modify(root[i-1],num,1,cnt);
}
while(m--){
int l,r,k;
cin>>l>>r>>k;
int ans=check(root[r],root[l-1],1,cnt,k);
cout<<c[ans]<<endl;
}
return 0;
}