#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#define ll long long
using namespace std;
const int N = 2e6+10;
inline int read(){
int ref=0,x=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')x=-1;ch=getchar();}
while(isdigit(ch)){ref=ref*10+ch-'0';ch=getchar();}
return ref*x;
}
int n,m,tot;
ll a[N],b[N];
ll T[N<<4],st[N<<4],ls[N<<4],rs[N<<4]; /*T[i] 表示第 i棵线段树的根节点的编号*/
/*st[i] 表示 i这个编号的节点的值是多少*/
ll build(ll l,ll r){
ll rt=++tot;
if(l<r){
ll mid=(l+r)>>1;
ls[rt]=build(l,mid);
rs[rt]=build(mid+1,r);
}
return rt;
}
ll updata(ll pre,ll l,ll r,ll x){ /* rt->pre */
ll rt=++tot;
ls[rt]=ls[pre],rs[rt]=rs[pre],st[rt]=st[pre]+1; /*将新的 rt节点的儿子接向上一棵线段树节点 pre的儿子*/
/*也就是先继承*/
if(l<r){
ll mid=(l+r)>>1;
if(x<=mid) ls[rt]=updata(ls[pre],l,mid,x); /*再把需要修改的部分给接回到新开的线段树,而不需要修改的部分则继续和上一棵线段树同穿一条裤子*/
else rs[rt]=updata(rs[pre],mid+1,r,x); /*这就是主席树的精髓之处——省空间(同穿一条裤子)*/
}
return rt;
}
ll query(ll L,ll R,ll l,ll r,ll k){ /* L表示左边的线段树的根节点, R表示右边的线段树的根节点*/
if(l>=r) return l;
ll x=st[ls[R]]-st[ls[L]];
ll mid=(l+r)>>1;
if(x>=k) return query(ls[L],ls[R],l,mid,k);
else return query(rs[L],rs[R],mid+1,r,k-x);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) a[i]=read(),b[i]=a[i];
sort(b+1,b+1+n);
int len=unique(b+1,b+1+n)-b-1; /*去重后的数组长度*/
T[0]=build(1,len);
for(int i=1;i<=n;i++){
int temp=lower_bound(b+1,b+len+1,a[i])-b; /*在 b中查找第一个 >=a[i]的数的位置,temp就是 a的 rank*/
/*离散化有 2种方法,这种二分法的好处是容易处理带有重复的数的数组*/
T[i]=updata(T[i-1],1,len,temp);
}
for(int i=1;i<=m;i++){
int x,y,z;
x=read(),y=read(),z=read();
ll t=query(T[x-1],T[y],1,len,z);
printf("%d
",b[t]);
}
return 0;
}