题意:给出n个数,m次询问,[x,y]内第k小的数时多少?n<=1e5,m<=5000
主席树:对原序列的每个前缀i都建立一个线段树 维护值域[l,r]中的每个数,在前缀i的出现次数.
此时l,r不在表示为下标,l,r为第l~r小的数
先将原序列离散化后,更新出主席树
求[x,y]内第k大的数?
若第1~mid大的数在[x,y]中出现的次数sum>=k,则[x,y]的第k大就在左子树中,否则[x,y]第k大为右子树的第k-sum个
由于主席树保存了第l~r大的数在任意前缀y和x-1的的出现次数,则第l~mid大的数在区间[x,y]的出现次数为T[T[y].l].sum-T[T[x-1].l].sum
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=2e5+20;
int n,m,a[N],x,y,k;
int root[N],cnt;
struct node{
int l,r,sum;
//此时l,r不在表示下标,sum表示第l~r大的数的出现次数
}T[N*40];
vector<int> v;
int getid(int x)//离散化
{
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int build(int l,int r)
{
int rt=++cnt;
T[rt].sum=0;
T[rt].l=T[rt].r=0;
if(l==r) return rt;
int mid=(l+r)>>1;
T[rt].l=build(l,mid);//l保存左子树结点号
T[rt].r=build(mid+1,r);
return rt;
}
void update(int l,int r,int &x,int y,int k)//根据第i个数是第几大来更新
{
T[++cnt]=T[y],T[cnt].sum++,x=cnt;
if(l==r) return;
int mid=(l+r)>>1;
if(mid>=k)
update(l,mid,T[x].l,T[y].l,k);
else
update(mid+1,r,T[x].r,T[y].r,k);
}
int query(int l,int r,int x,int y,int k)
{
if(l==r) return l;
int mid=(l+r)>>1;
int sum=T[T[y].l].sum-T[T[x].l].sum;
if(sum>=k)
return query(l,mid,T[x].l,T[y].l,k);
else
return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
int main()
{
while(cin>>n>>m)
{
cnt=0;
build(1,n);
v.clear();
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),v.push_back(a[i]);
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++)//前缀1~i和1~i-1只有一条链不同,那么root[i]其它结点只要用前一棵树的结点即可
update(1,n,root[i],root[i-1],getid(a[i])); //
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&k);
int ans=query(1,n,root[x-1],root[y],k);
printf("%d\n",v[ans-1]);//输出第k大离散化前的值
}
}
return 0;
}