主席树不采用\(p*2,p*2+1\)的方式来表示左右儿子,而是需要动态开点地保存左右儿子的编号,从而节约空间。
那我们从根节点开始,计算左孩子范围的数字\(num\),如果\(k\leq num\),说明第\(k\)小的数字在左子树中,递归进入左子树,否则进入右子树。
空间分析:
至此,问题解决,详见代码。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int a[maxn], num[maxn], n, m, len;
int sum[maxn<<5]; //sum(i)存储根为i的子树的大小
int ls[maxn<<5]; //左儿子
int rs[maxn<<5]; //右儿子
int rt[maxn<<5]; //根节点
int tot; //一共出现多少个根
int build(int l, int r)
{
int root = ++tot;
if(l == r) return root;
int mid = (l + r) >> 1;
ls[root] = build(l, mid);
rs[root] = build(mid+1, r);
return root; //返回这课子树的根节点
}
//插入操作
int update(int pre, int l, int r, int k)
{
int root = ++tot;
ls[root] = ls[pre], rs[root] = rs[pre], sum[root] = sum[pre] + 1;
if(l == r) return root;
int mid = (l + r) >> 1;
//更改左子树或右子树
if(k <= mid) ls[root] = update(ls[pre], l, mid, k);
else rs[root] = update(rs[pre], mid+1, r, k);
return root;
}
//查询操作
int query(int u, int v, int l, int r, int k)
{
if(l == r) return l;
int x = sum[ls[v]] - sum[ls[u]];
int mid = (l + r) >> 1;
if(k <= x) return query(ls[u], ls[v], l, mid, k);
else return query(rs[u], rs[v], mid+1, r, k - x);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
num[i] = a[i];
}
//离散化
sort(num+1, num+1+n);
len = unique(num+1, num+1+n) - num - 1;
rt[0] = build(1, len);
for(int i = 1; i <= n; i++)
{
int t = lower_bound(num+1, num+1+len, a[i]) - num;
rt[i] = update(rt[i-1], 1, len, t);
}
int l, r, k;
while(m--)
{
scanf("%d%d%d", &l, &r, &k);
int ans = query(rt[l-1], rt[r], 1, len, k);
printf("%d\n", num[ans]);
}
return 0;
}
原文:https://www.cnblogs.com/zxytxdy/p/11956952.html