这道题目要求我们求出一个区间内的第k大,静态的
首先可以考虑建立n棵权值线段树,第i棵线段树[a,b]记录数组1-i的区间中,数字a-b一共出现了多少次,也就是说这棵线段树的叶子节点的位置表示的是这个数字在1-i这个区间出现的次数
如果要求区间[a,b]内的第k大,我们可以利用前缀和的思想,将第b棵线段树和第a-1棵线段树相减,那么就可以得到了[a,b]这个区间内的权值线段树,然后就可以按照线段树的思想,去query第k的位置就可以了
然而,我们需要开n棵线段树,显然空间消耗过大
我们通过观察线段树,可以发现每两个相邻的线段树有很大的重复性,考虑是否在建新的一棵树的时候能否利用之前的位置,减少点的建立,合并在一棵大树上
考虑到每次建下一棵树的时候,实际上只加上了一个数值,也就是对于这个数值对应的叶子节点要有修改(+1),然后向上更新修改即可,也就是每次需要新建lgN个点,然后其余的位置就可以和上一次的位置相连
到此,树的建立就完成了
然后就是查询操作了,我们可以进行和线段树上查询基本一样的操作了,就是每次的数量用前缀和的差进行计算了
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1e5+5;
int tot,n,m,sum[maxn<<5],has[maxn],a[maxn],fa[maxn],l[maxn<<5],r[maxn<<5];
int build(int L,int R)
{
int rt=++tot;
sum[rt]=0;
if(L<R)
{
int mid=(L+R)>>1;
l[rt]=build(L,mid);
r[rt]=build(mid+1,R);
}
return rt;
}
int update(int pre,int L,int R ,int x)
{
int rt=++tot;
l[rt]=l[pre]; r[rt]=r[pre]; sum[rt]=sum[pre]+1;
if(L<R)
{
int mid=(L+R)>>1;
if(x<=mid)
l[rt]=update(l[pre],L,mid,x);
else r[rt]=update(r[pre],mid+1,R,x);
}
return rt;
}
int query(int left ,int right ,int L,int R,int x)
{
if(L>=R) return L;
int mid=(L+R)>>1;
int num=sum[l[right]]-sum[l[left]];
if(num>=x)
return query(l[left],l[right],L,mid,x);
else return query(r[left],r[right],mid+1,R,x-num);
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),has[i]=a[i];
sort(has+1,has+n+1);
int d=unique(has+1,has+n+1)-has-1;
fa[0]=build(1,d);
for(int i=1;i<=n;i++)
{
int x=lower_bound(has+1,has+d+1,a[i])-has;
fa[i]=update(fa[i-1],1,d,x);
}
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
int t=query(fa[x-1],fa[y],1,d,z);
printf("%d\n",has[t]);
}
return 0;
}