luogu P3834 【模板】可持久化线段树 1(主席树) 查询区间 [l, r] 内的第 k 小/大值
————————————————
版权声明:本文为CSDN博主「ModestCoder_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ModestCoder_/article/details/90139481
//主席树 //难以处理区间修改操作,很难处理懒标记 //l,r代表左右子节点的下标 //cnt表示当前区间中一共多少个数 //离散化 //在数值上建立线段树,维护每个数值区间中一共多少个数 // #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> using namespace std; const int N = 200010; int n, m; int a[N]; vector<int> nums; struct Node { //左儿子和右儿子的 //左边都是比l+r>>1小的 //右边都是大的 int l, r; //区间里一共多少个数 int cnt; }tr[N << 5]; //根节点 树里可用节点的下标 int root[N << 5], idx; //求离散化之后的值 int find(int x) { return lower_bound(nums.begin(), nums.end(), x) - nums.begin(); } //建树,原始的,没插入数字的树 int build(int l, int r) { //建立新的 int p = ++ idx; //如果是叶节点 if (l == r) return p; //左右儿子 int mid = l + r >> 1; tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r); return p; } //原来的根节点,左右边界, 插入的位置(原来的数字离散化后的排名) // 0,nums.size()-1, int insert(int p, int l, int r, int x) { //从0号点开始查找 //root的cnt表示的是1到num.size()-1之间数字的个数 //所以root这个点肯定会变化 //所以要开新的点 int q = ++ idx; //在没有找到之前 //先赋值过来,上一个版本的根节点 // tr[q] = tr[p]; //如果是叶节点,找到要更新的店 //也就是找到x这个位置了 //就是去找要找插入的点 if (l == r) { //新点cnt++ tr[q].cnt ++ ; return q; } //如果没有找到要更新的点 //当前点往下递归, int mid = l + r >> 1; //递归 //更新沿途的点 if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x); else tr[q].r = insert(tr[p].r, mid + 1, r, x); //左右儿子的cnt的和 tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt; return q; } int query(int q, int p, int l, int r, int k) { //如果到叶节点了 ,就返回 if (l == r) return r; //左区间表示 //1到 tr[q].l中数字的个数- 1到tr[p].l中数字的个数 //这些数字都是属于 要 查询的区间中的数字 int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt; //中点,左右儿子的中点 int mid = l + r >> 1; //如果k<=cnt //也就是要查询的区间(下标)中的数字插入到左半边的数字大于等于k //那么答案对应的离散化之后的坐标就一定在左边 if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k); //或者都在右边,这里k要更新为k-cnt,要除去左半边的 else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt); } int main() { scanf("%d%d", &n, &m); //读入数据 for (int i = 1; i <= n; i ++ ) { scanf("%d", &a[i]); nums.push_back(a[i]); } //离散化 //会排序 sort(nums.begin(), nums.end()); nums.erase(unique(nums.begin(), nums.end()), nums.end()); //第一个版本的线段树,是root[0] root[0] = build(0, nums.size() - 1); for (int i = 1; i <= n; i ++ ) //第i个版本的线段树 //是和i-1版本的线段树比较,左右边界是0到 nums.size() - 1,在find(a[i])这个位置加1 // 对应a[i]的离散值 root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i])); while (m -- ) { int l, r, k; scanf("%d%d%d", &l, &r, &k); //要返回离散化前的值 printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]); } return 0; }