|
|
// http: // poj.org/problem?id=2104
|
|
|
//#include <bits/stdc++.h>
|
|
|
#include <iostream>
|
|
|
#include <string.h>
|
|
|
#include <stdio.h>
|
|
|
#include <vector>
|
|
|
#include <map>
|
|
|
#include <queue>
|
|
|
#include <algorithm>
|
|
|
#include <math.h>
|
|
|
#include <cstdio>
|
|
|
|
|
|
const int N = 100010;
|
|
|
using namespace std;
|
|
|
|
|
|
//建立一颗权值线段树,每个点存储的信息为该值域区间存在的数的个数
|
|
|
struct Node {
|
|
|
int l, r; //分别表示左右节点编号
|
|
|
int cnt; //区间内数字个数
|
|
|
} tr[N * 4 + N * 17]; //初始骨架N * 4 再加上最多M次插入一共修改MlogM个节点
|
|
|
//也见过有的大佬说,直接开N*32就行
|
|
|
|
|
|
vector<int> nums; //用于离散化的数组,由于数的范围很大,但是不能开那么大的空间,所以需要离散化
|
|
|
int root[N], idx; // root记录根节点的版本号【root[r]表示插入了第r个数版本的线段树】, idx记录树节点的版本号
|
|
|
int a[N]; //原始数组
|
|
|
int n, m;
|
|
|
|
|
|
//离散化二分查找
|
|
|
int find(int x) {
|
|
|
return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
|
|
|
}
|
|
|
|
|
|
//构建新版本的线段树,返回新创建的节点号
|
|
|
//注意里与普通版本的线段树不一样,普通线段树是的左儿子编号是父亲的编号*2,右儿子的编号是父亲的编号*2+1
|
|
|
//而主席树的节点编号是通过tot++随用随算的,因为多个版本,不能沿用原来的处理办法,那样就空着内存节点的太多了
|
|
|
int build(int l, int r) { // l,r是指数据管辖区间
|
|
|
int p = ++idx; //生成节点号
|
|
|
if (l == r) return p; //到叶子节点,直接返回新产生的节点号,不再递归
|
|
|
int mid = (l + r) >> 1; // 2x,2x+1,静态线段树构建办法
|
|
|
tr[p].l = build(l, mid); //构建左子树
|
|
|
tr[p].r = build(mid + 1, r); //构建右子树
|
|
|
return p;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @brief 对原始数据,通过一个一个的insert方式存入主席树,以达到生成每个版本线段树的目标
|
|
|
*
|
|
|
* @param p 上一个版本的版本号
|
|
|
* @param l
|
|
|
* @param r
|
|
|
* @param x
|
|
|
* @return int
|
|
|
*/
|
|
|
int insert(int p, int l, int r, int x) {
|
|
|
int q = ++idx; // 1、获取到可用节点编号
|
|
|
tr[q] = tr[p]; // 2、旧版本啥样我啥样
|
|
|
if (l == r) { // 3、第x大位置的数字个数增加了1个
|
|
|
tr[q].cnt++;
|
|
|
return q;
|
|
|
}
|
|
|
int mid = (l + r) >> 1;
|
|
|
if (x <= mid) // 4、x小于等于mid,向左半部递归
|
|
|
tr[q].l = insert(tr[p].l, l, mid, x);
|
|
|
else // 5、x大于mid,向右半部递归
|
|
|
tr[q].r = insert(tr[p].r, mid + 1, r, x);
|
|
|
|
|
|
//更新区间内数字个数,相当于pushup
|
|
|
tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;
|
|
|
return q; //返回新创建的节点编号
|
|
|
}
|
|
|
/**
|
|
|
* @brief 在q这个版本的线段树中,它的前序版本是p,管辖范围是[l,r],求第k小数
|
|
|
*
|
|
|
* @param q 当前版本号
|
|
|
* @param p 前序版本号
|
|
|
* @param l 左边界
|
|
|
* @param r 右边界
|
|
|
* @param k 第k小
|
|
|
* @return int 是什么数字
|
|
|
*/
|
|
|
int query(int q, int p, int l, int r, int k) {
|
|
|
if (l == r) return r; //到叶子节点了,返回是第l个数字,nums返回原始数字
|
|
|
//当前版本左儿子区间内的数字个数,减去,前序版本左儿子区间内的数字个数,
|
|
|
//描述了在从l到r这两个版本间增加的数字数量,类似于前缀和思想
|
|
|
int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;
|
|
|
int mid = (l + r) >> 1;
|
|
|
if (k <= cnt)
|
|
|
return query(tr[q].l, tr[p].l, l, mid, k);
|
|
|
else
|
|
|
return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
|
|
|
}
|
|
|
|
|
|
int main() {
|
|
|
cin >> n >> m;
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
cin >> a[i];
|
|
|
nums.push_back(a[i]);
|
|
|
}
|
|
|
//离散化 为什么要离散化?是因为值域范围1e9,太大
|
|
|
sort(nums.begin(), nums.end());
|
|
|
nums.erase(unique(nums.begin(), nums.end()), nums.end());
|
|
|
|
|
|
//构建版本0,只有版本0需要构建
|
|
|
//因为离散化后的范围区间是0~nums.size()-1
|
|
|
//看来构建线段树的l,r其实是指的原始数组的索引位置
|
|
|
root[0] = build(0, nums.size() - 1);
|
|
|
|
|
|
//构建不同版本的线段树
|
|
|
for (int i = 1; i <= n; i++)
|
|
|
// 1、每个新版本,都只能在上一个版本基础上进行修改获取到
|
|
|
// 2、现在这个新版本,也和上一个版本的范围是一样的,也是[0,nums.size()-1]
|
|
|
// 3、find(a[i]):通过二分获取a[i]这个数据现在离散化后所在的位置
|
|
|
root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));
|
|
|
|
|
|
while (m--) {
|
|
|
int l, r, k;
|
|
|
cin >> l >> r >> k;
|
|
|
//查询[l,r]区间中的第k大数,利用前缀和的思想,从[l ~ r]中的数据剔除掉[1 ~ l-1]的数据,
|
|
|
//不同版本的线段树的结构都是一样的,所以初始都是从0到nums.size()这个范围当中找
|
|
|
printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);
|
|
|
}
|
|
|
return 0;
|
|
|
} |