题目链接:https://www.luogu.org/problemnew/show/P3834
题目大意:如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
主席树:
首先将值离散化之后,构建一颗值域线段树
储存区间和,区间[l, r]表示
0版本的线段树是空树
每次在值域上增加1就重构一颗线段树
很显然,任意两颗相邻线段树的值得和差为1
而相同的区间内要么相等要么多1
那么,我们也很容易的可以推出,区间第k大可以通过第r版本和第(l-1)版本的线段树算出来 。
类型如:区间第k大元素,分治求解。
每次计算左儿子
如果r的左儿子已经比(l-1)的左儿子多出来的和大于k
那么在左儿子查找k大值
否则则在右儿子上查找第(k-val)大值,其中,val是和的差
#include<bits/stdc++.h>
#define LL long Long
using namespace std;
const int maxn=2e5+10;
int L[maxn*20], R[maxn*20], sum[maxn*20];
int n, q, m ,cnt = 0;
int a[maxn], b[maxn], root[maxn];
#define mid (l+r)/2
int JS(int l, int r)
{
int rt=++cnt;
sum[rt]=0;
if(l<r)
{
L[rt]=JS(l ,mid);
R[rt]=JS(mid+1, r);
}
return rt;
}
int gx(int i, int l, int r, int x)
{
int rt=++cnt;
L[rt]=L[i], R[rt]=R[i], sum[rt]=sum[i]+1;//更新
if(l<r)
{
if(x<=mid)
{
L[rt]=gx(L[i], l, mid, x);
}
else
{
R[rt]=gx(R[i], mid+1, r, x);
}
}
return rt;
}
int cx(int u, int v, int l, int r, int k)
{
if(l>=r)
{
return l; //找到区间
}
int x=sum[L[v]]-sum[L[u]];
if(x>=k)
{
return cx(L[u], L[v], l, mid, k);//递归查找
}
else
{
return cx(R[u], R[v], mid+1, r, k-x);//放4递归查找
}
}
int LSH() //离散化
{
sort(b+1, b+1+n);
m = unique(b+1, b+1+n)-b-1;
for (int i = 1; i <= n; i ++)
{
a[i] = lower_bound(b+1, b+1+m, a[i])-b;
}
}
int main()
{
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i ++)
{
scanf("%d", &a[i]);
b[i] = a[i];
}
LSH();
root[0] = JS(1, cnt);
for(int i=1;i<=n;i++)
{
root[i]=gx(root[i-1], 1, m, a[i]);//更新
}
while (q --)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
int t = cx(root[x-1], root[y], 1, m, z);
printf("%d\n", b[t]);
}
return 0;
}