//主席树 权值线段树+可持久化
//权值线段树:在此处指各个数字在某个区间内出现的次数
//那么第一棵权值线段树会记录[1,1]的数字出现次数
//第n棵权值线段树会记录[1,n]的数字出现次数
//例:数列为110001
//第一棵权值线段树记录为tree1[0]=0 tree1[1]=1
//第二棵权值线段树记录为tree2[0]=0 tree2[1]=2
//第六棵权值线段树记录为tree6[0]=3 tree6[1]=3
//那么要求区间为[3,5]的数字出现次数可拿第五棵权值线段树减去第(三减一)棵权值线段树
//此处运用前缀和思想
//将多棵权值线段树的公共点合为一个点可减少空间复杂度
//求区间第k小即可求出区间数字出现次数后递归操作
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,cnt,b[200001],root[200001];//b[]离散化后的值 root[]根的编号
struct uio{
int num,id;
}a[200001];//离散化辅助结构体
struct rty{
int ls,rs,siz;//左儿子,右儿子,区间元素个数
}tree[5000001];//权值线段树 递增使得左儿子表示的值小于右儿子表示的值
bool cmp(uio x,uio y)
{
return x.num<y.num;
}
void update(int l,int r,int k,int &now)//新建节点的左右儿子,新建节点值,当前节点
{
tree[++cnt]=tree[now];
now=cnt;
tree[now].siz++;
if(l==r)
return;
int mid=(l+r)/2;
if(k<=mid)//新建节点在左子树
update(l,mid,k,tree[now].ls);
else update(mid+1,r,k,tree[now].rs);//新建节点在右子树
}
int query(int l,int r,int x,int y,int k)//左右儿子,两棵权值线段树编号,第k大
{
if(l==r)
return l;
int dif=tree[tree[y].ls].siz-tree[tree[x].ls].siz;//见本代码第9行
int mid=(l+r)/2;
if(k<=dif)//左儿子的出现次数已大于等于k 说明k在左子树
return query(l,mid,tree[x].ls,tree[y].ls,k);
else return query(mid+1,r,tree[x].rs,tree[y].rs,k-dif);//k在右子树
//k不在左子树 但是左子树代表的数的出现次数包含在k中 因此要减去(同平衡树)
}
void do_something()
{
for(int i=1;i<=m;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
printf("%d\n",a[query(1,n,root[u-1],root[v],w)].num);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].num);
a[i].id=i;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
b[a[i].id]=i;//离散化
for(int i=1;i<=n;i++)
{
root[i]=root[i-1];//暂时将新的根节点赋为原根节点的编号
update(1,n,b[i],root[i]);//新建一棵权值线段树
}
do_something();
return 0;
}