静态查询区间第k小问题。
我们先要知道,权值线段树可以维护整体区间的桶,查询整体区间第k小。
虽然这题是查询子区间[l,r]的第k小,但我们可以转化成整体区间第k小问题:
我们对每个前缀都建一棵权值线段树,用于维护出现个数。
则[1,r]区间对应的线段树减去[1,l-1]对应的线段树后,得到的那棵线段树就是以[l,r]为整体区间的权值线段树。
那么[l,r]区间内的第k小值就等于 :[1,r]区间对应的线段树减去[1,l-1]对应的线段树后,得到的那棵线段树的整体区间第k小。(这里是线段树合并的思想,可以证明这两颗线段树是具有可加性的)
(两棵线段树相减指对应的节点的值相减)
但是如果老老实实对每个前缀都建树的话一共有2e5棵树,肯定不行。
而且我们注意到第i棵线段树和第i-1棵线段树相比其实就是单点修改,所以这时候主席树就派上用场了。
我们先建一棵空树,版本为0,
然后依次插入n个点,第i次插入得到的线段树版本为i 。
建树方法有两种,一种是把数据离散化后建树,
一种是动态开点建可持久化权值线段树。(名字瞎编的,大概就是这两种线段树的交集?)
总结一下,这题本质上就是利用了前缀建树的线段树可加性建主席树,把每次询问的[l,r]区间转化成了一棵权值线段树的根节点区间,于是问题就成了在相减得到的那颗权值线段树里面,求整体区间第k小问题。
实现过程就看代码吧。
//离散化建树
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5+7;
const int mod = 1e9+7;
const ll inf = 34359738370;
int n,m;
int a[maxn],h[maxn],size;//h为离散化后的数组 size为离散化去重后的个数 也即是线段树建树区间长度[1,size]
int tree[maxn<<5],lc[maxn<<5],rc[maxn<<5],cnt=0;
//对离散化后的数组建树后, [l,r]表示离散化数组h[l....r]中每个数在原先数组出现次数之和
int root[maxn];//每个版本的根序号 第i棵树就是对第i个前缀建的树 根序号为root[i]
//给定序列 q次查询 [l,r]区间的第k小
//对每个前缀序列建线段树 [l,r]区间第k小就相当于[1,r]对应的线段树减去[1,l-1]对应的线段树之后得到的线段树中的第k小
//线段树1-线段树2 指 对应每个节点的值相减
void build(int &rt,int l,int r)
{
rt=++cnt;
if(l == r)
{
return ;
}
int mid=(l+r)>>1;
build(lc[rt],l,mid);
build(rc[rt],mid+1,r);
}
void updata(int &rt,int last,int l,int r,int pos)//last线段树版本上的单点修改
{
rt=++cnt;
tree[rt]=tree[last]+1;//每次给桶加入一个数 也就是rt版本要修改的区间比last版本多1个数
lc[rt]=lc[last],rc[rt]=rc[last];
if(l == r) return ;//叶子节点已经+1了
int mid=(l+r)>>1;
if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos);
else updata(rc[rt],rc[last],mid+1,r,pos);
}
int kth(int ltree,int rtree,int l,int r,int k)//ltree对应区间是[1,l-1] rtree对应区间是[1,r]
//查询的本质就是在两棵线段树相减得到的线段树上查询整体区间第k小
{
if(l == r) return l;//当前线段树[l,l]区间对应在离散化数组的数就是第k小数
int num=tree[lc[rtree]]-tree[lc[ltree]];
//num表示 两树相减得到的线段树的左子树区间的值 也就是桶里面数的个数 (和权值线段树查kth是一样的思路)
int mid=(l+r)>>1;
if(k<=num) return kth(lc[ltree],lc[rtree],l,mid,k);
else return kth(rc[ltree],rc[rtree],mid+1,r,k-num);
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
h[i]=a[i];
}
//离散化步骤 先排序后去重
sort(h+1,h+n+1);
size=unique(h+1,h+n+1)-h-1;//返回的是去重后末尾的下一位 所以要-1
build(root[0],1,size);//第0版本的线段树是空树
for(int i=1;i<=n;i++)
{
int index=lower_bound(h+1,h+size+1,a[i])-h;
updata(root[i],root[i-1],1,size,index);//h[index]这个值在桶中出现的次数+1
}
while(m--)
{
int x,y,k;
scanf("%d %d %d",&x,&y,&k);
printf("%d\n",h[kth(root[x-1] ,root[y] ,1,size,k)]);
}
return 0;
}
//动态开点可持久化权值线段树
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5+7;
const ll inf = 34359738370;
const int INF = 1e9+5;
int a[maxn];
int tree[maxn*105],lc[maxn*105],rc[maxn*105],cnt=0,root[maxn];
int n,m;
inline void updata(int &rt,int last,int l,int r,int pos)
{
rt=++cnt;
tree[rt]=tree[last]+1;
lc[rt]=lc[last],rc[rt]=rc[last];
if(l == r) return;
int mid=(l+r)>>1;
if(pos<=mid) updata(lc[rt],lc[last],l,mid,pos);
else updata(rc[rt],rc[last],mid+1,r,pos);
}
int query(int rt1,int rt2,int l,int r,int k)
{
if(l == r) return l;
int num=tree[lc[rt2]]-tree[lc[rt1]];
int mid=(l+r)>>1;
if(k<=num) return query(lc[rt1],lc[rt2],l,mid,k);
else return query(rc[rt1],rc[rt2],mid+1,r,k-num);
}
int main()
{
scanf("%d %d",&n,&m);
//动态开点 不需要建树 直接插入就好 而这里插入过程也是线段树合并过程
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
updata(root[i],root[i-1],-INF,INF,a[i]);
}
while(m--)
{
int x,y,k;
scanf("%d %d %d",&x,&y,&k);
printf("%d\n",query(root[x-1],root[y],-INF,INF,k));
}
return 0;
}