#include #include #include using namespace std; //快读 int read() { int x = 0, f = 1; char ch = getchar(); while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); } while (ch >= '0' && ch <= '9') { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); } return x * f; } const int N = 2e5 + 10; int n, m; int a[N]; vector ys; struct Node { int l, r, cnt; } tr[N << 5]; int root[N], idx; int find(int x) { return lower_bound(ys.begin(), ys.end(), x) - ys.begin(); } //经典的主席树插入 void insert(int &u, int l, int r, int x) { tr[++idx] = tr[u]; //新开一个节点idx++,将新节点指向旧的tr[u] u = idx; //因为是引用,为了回传正确值,需要u=idx-1 tr[u].cnt++; //新节点的cnt,因为多插入了一个数字,所以个数+1,这样处理的话,省去了pushup if (l == r) return; //如果已经到了叶子节点,上面的操作就足够了,可以直接返回,否则需要继续向下递归 int mid = (l + r) >> 1; if (x <= mid) insert(tr[u].l, l, mid, x); //因为tr[u]进入本函数时,最先把旧的复制过来,所以tr[u].l也是上一个版本的左儿子节点 else insert(tr[u].r, mid + 1, r, x); } int query(int p, int q, int l, int r, int k) { if (l == r) return l; int mid = (l + r) >> 1; int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt; if (k <= cnt) return query(tr[p].l, tr[q].l, l, mid, k); else return query(tr[p].r, tr[q].r, mid + 1, r, k - cnt); } int main() { //文件输入输出 #ifndef ONLINE_JUDGE freopen("P3834.in", "r", stdin); #endif n = read(), m = read(); for (int i = 1; i <= n; i++) { a[i] = read(); ys.push_back(a[i]); } //数据范围太大,直接建线段树会MLE,但是比较稀疏,可以离散化后用相对应的序号,数据量就小了 sort(ys.begin(), ys.end()); ys.erase(unique(ys.begin(), ys.end()), ys.end()); // 0号版本没有内容时,主席树是不需要进行build的,强行build时,可能会有部分测试点TLE // 0号版本有内容时,主席树是需要build的,不build,初始值无法给上 //主席树的数字增加,每增加一个,就相当于增加了一个版本root[i]记录了版本i的根节点 for (int i = 1; i <= n; i++) { root[i] = root[i - 1]; insert(root[i], 0, ys.size() - 1, find(a[i])); } while (m--) { int l, r, k; l = read(), r = read(), k = read(); printf("%d\n", ys[query(root[l - 1], root[r], 0, ys.size() - 1, k)]); } return 0; }