西安多校集训-主席树
前言
这不就是寒假集训时 dbg 学长讲的线段树上二分吗?感觉好像。
思想
可持久化权值线段树简称主席树。
先说可持久化:
可持久化
我们发现如果只有单点修改,一颗线段树最多被修改 \(\log n\) 个结点(也就是一条链),所以直接新建这 \(\log n\) 个结点,将其指向根节点的同时,指向对应的过去结点。这样就可以做到时空均为 \(O(n\log n)\) 的可接受复杂度。当然需要动态开点。
主席树
可以用于静态查询区间第 k 小或第 k 大。
建立一棵权值线段树,树上维护每个数的数量。每次加入一个结点就新开一个版本的线段树,然后进行树上二分,运用前缀和思想。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2e5+50;
int ls[N<<5],rs[N<<5],sum[N<<5];
int ver[N<<5],tot;
int n,m;
int a[N],b[N],len;
int build(int l,int r){
int now=++tot;
if(l==r) return now;
int mid=(l+r)>>1;
ls[now]=build(l,mid);
rs[now]=build(mid+1,r);
return now;
}
int insert(int l,int r,int pre,int k){
int now=++tot;
ls[now]=ls[pre];rs[now]=rs[pre];sum[now]=sum[pre]+1;
if(l==r) return now;
int mid=(l+r)>>1;
if(k<=mid){
ls[now]=insert(l,mid,ls[pre],k);
}else{
rs[now]=insert(mid+1,r,rs[pre],k);
}
return now;
}
int query(int now,int pre,int l,int r,int k){
if(l==r) return l;
int mid=(l+r)>>1;
int x=sum[ls[now]]-sum[ls[pre]];
if(k<=x){
return query(ls[now],ls[pre],l,mid,k);
}else{
return query(rs[now],rs[pre],mid+1,r,k-x);
}
}
int find(int x){
return lower_bound(b+1,b+1+len,x)-b;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
len=unique(b+1,b+1+n)-b-1;
ver[0]=build(1,len);
for(int i=1;i<=n;i++){
ver[i]=insert(1,len,ver[i-1],find(a[i]));
}
for(int i=1;i<=m;i++){
int l,r,k;
cin>>l>>r>>k;
cout<<b[query(ver[r],ver[l-1],1,len,k)]<<'\n';
}
return 0;
}
哦原来是用到线段树上二分了啊,那没事了。

浙公网安备 33010602011771号