【题目来源】
https://www.luogu.com.cn/problem/P3834
【题目描述】
这是个非常经典的可持久化权值线段树入门题------静态区间第 k 小。
如题,给定 n 个整数构成的序列 a,将对于指定的闭区间 l,r 查询其区间内的第 k 小值。
【输入格式】
第一行包含两个整数,分别表示序列的长度 n 和查询的个数 m。
第二行包含 n 个整数,第 i 个整数表示序列的第 i 个元素 ai。
接下来 m 行每行包含三个整数 l,r,k , 表示查询区间 l,r 内的第 k 小值。
【输出格式】
对于每次询问,输出一行一个整数表示答案。
【输入样例】
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
【输出样例】
6405
15770
26287
25957
26287
【数据范围】
对于 10% 的数据,满足 1≤n,m≤10。
对于 25% 的数据,满足 1≤n,m≤10^3。
对于 40% 的数据,满足 1≤n,m≤10^5。
对于 50% 的数据,满足 1≤n,m≤2×10^5,0≤ai≤2×10^5。
对于所有数据,满足 1≤n,m≤2×10^5,0≤ai≤10^9,1≤l≤r≤n,1≤k≤r−l+1。
【算法分析】
● 在信息学竞赛中,用于++求解静态区间第 k 小数的可持久化权值线段树++ ,俗称主席树。所谓++可持久化,指保存历史版本++ 。所谓权值线段树,是将数值的值域作为划分依据,树上每个节点对应一段值域区间,维护该值域区间内元素的总出现次数。
● 集合关系:主席树 ⊂ 可持久化线段树
【算法代码一】
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int root[N],a[N],tot;
vector<int> v;
struct Node {
int L,R,cnt;
} tr[N*40];
int getid(int x) {
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int build(int le,int ri) {
int p=++tot;
if(le==ri) return p;
int mid=(le+ri)/2;
tr[p].L=build(le,mid);
tr[p].R=build(mid+1,ri);
return p;
}
int update(int pre,int le,int ri,int x) {
int p=++tot;
tr[p]=tr[pre];
if(le==ri) {
tr[p].cnt++;
return p;
}
int mid=(le+ri)/2;
if(x<=mid) tr[p].L=update(tr[pre].L,le,mid,x);
else tr[p].R=update(tr[pre].R,mid+1,ri,x);
tr[p].cnt=tr[tr[p].L].cnt+tr[tr[p].R].cnt;
return p;
}
int query(int u,int v,int le,int ri,int k) {
if(le==ri) return le;
int mid=(le+ri)/2;
int cnt=tr[tr[v].L].cnt-tr[tr[u].L].cnt;
if(k<=cnt) return query(tr[u].L,tr[v].L,le,mid,k);
else return query(tr[u].R,tr[v].R,mid+1,ri,k-cnt);
}
int main() {
int n,m;
cin>>n>>m;
for(int i=1; i<=n; i++) {
cin>>a[i];
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
int len=v.size();
root[0]=build(1,len);
for(int i=1; i<=n; i++) {
root[i]=update(root[i-1],1,len,getid(a[i]));
}
while(m--) {
int le,ri,k;
cin>>le>>ri>>k;
int id=query(root[le-1],root[ri],1,len,k);
cout<<v[id-1]<<endl;
}
return 0;
}
/*
in:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
out:
6405
15770
26287
25957
26287
*/
【算法代码二】
算法代码二仅将算法代码一中的结构体成员变量 L、R 分别换成了 le、ri,并对代码中其他地方做了相应修改。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int root[N],a[N],tot;
vector<int> v;
struct Node {
int le,ri,cnt;
} tr[N*40];
int getid(int x) {
return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
int build(int le,int ri) {
int p=++tot;
if(le==ri) return p;
int mid=(le+ri)/2;
tr[p].le=build(le,mid);
tr[p].ri=build(mid+1,ri);
return p;
}
int update(int pre,int le,int ri,int x) {
int p=++tot;
tr[p]=tr[pre];
if(le==ri) {
tr[p].cnt++;
return p;
}
int mid=(le+ri)/2;
if(x<=mid) tr[p].le=update(tr[pre].le,le,mid,x);
else tr[p].ri=update(tr[pre].ri,mid+1,ri,x);
tr[p].cnt=tr[tr[p].le].cnt+tr[tr[p].ri].cnt;
return p;
}
int query(int u,int v,int le,int ri,int k) {
if(le==ri) return le;
int mid=(le+ri)/2;
int cnt=tr[tr[v].le].cnt-tr[tr[u].le].cnt;
if(k<=cnt) return query(tr[u].le,tr[v].le,le,mid,k);
else return query(tr[u].ri,tr[v].ri,mid+1,ri,k-cnt);
}
int main() {
int n,m;
cin>>n>>m;
for(int i=1; i<=n; i++) {
cin>>a[i];
v.push_back(a[i]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
int len=v.size();
root[0]=build(1,len);
for(int i=1; i<=n; i++) {
root[i]=update(root[i-1],1,len,getid(a[i]));
}
while(m--) {
int le,ri,k;
cin>>le>>ri>>k;
int id=query(root[le-1],root[ri],1,len,k);
cout<<v[id-1]<<endl;
}
return 0;
}
/*
in:
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
out:
6405
15770
26287
25957
26287
*/
【参考文献】