You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
4.9 KiB

2 years ago
// 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;
}