LG3834 可持久化线段树1
题意
给定\(N\)个整数构成的序列,将对于指定的闭区间查询其区间内的第\(K\)小值。
$n \leq 2 \times 10^5 $
思路
在\([l,r]\)区间内的数的个数,可以用\(sum[r]-sum[l]\)来计算,这样的话就很容易想到要开n棵权值线段树,但是一看范围,很显然会\(mle\),于是就有一个叫主席树的东西出现了。
当新插入一个数的时候,会发现,只有一条路径上的\(sum\)会发生变化,其实只要复制这一条路径上的结点就好了。
插入时,如果它对右儿子无影响,那么将它的右儿子指向原先的树,左儿子继续进行插入操作,反之亦然。查找时用到差分进行左右路径的选择,一直走下去就好了。
#include <bits/stdc++.h>
using namespace std;
const int N=200005;
int n,m,a[N],b[N],s1[N*40],s2[N*40],sum[N*40],T[N],l,r,x,tot,n2;
int update(int pre,int l,int r,int x){
int rt=++tot;
s1[rt]=s1[pre],s2[rt]=s2[pre],sum[rt]=sum[pre]+1;
if (l>=r) return rt;
int mid=(l+r)>>1;
if (x<=mid) s1[rt]=update(s1[pre],l,mid,x);
else s2[rt]=update(s2[pre],mid+1,r,x);
return rt;
}
int query(int u,int v,int l,int r,int x){
if (l==r) return l;
int mid=(l+r)>>1,tt=sum[s1[v]]-sum[s1[u]];
if (x<=tt) return query(s1[u],s1[v],l,mid,x);
else return query(s2[u],s2[v],mid+1,r,x-tt);
}
int main(){
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);
n2=unique(b+1,b+n+1)-b-1;
for (int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n2+1,a[i])-b;
T[i]=update(T[i-1],1,n2,a[i]);
}
for (int i=1;i<=m;i++){
scanf("%d%d%d",&l,&r,&x);
printf("%d\n",b[query(T[l-1],T[r],1,n2,x)]);
}
}
* 生而自由 爱而无畏 *