(模板)可持久化线段树 1(主席树)
题目背景
这是个非常经典的主席树入门题——静态区间第(k)小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定(n)个整数构成的序列,将对于指定的闭区间查询其区间内的第(k)小值。
输入格式
第一行包含两个正整数(n,m),分别表示序列的长度和查询的个数。
第二行包含(n)个整数,表示这个序列各项的数字。
接下来(m)行每行包含三个整数(l,r,k),表示查询区间([l,r])内的第(k)小值。
输出格式
输出包含(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
说明/提示
数据范围:
对于(20\%)的数据满足:(1 leq n,m leq 10)
对于(50\%)的数据满足:(1 leq n,m leq 10^3)
对于(80\%)的数据满足:(1 leq n,m leq 10^5)
对于(100\%)的数据满足:(1 leq n,m leq 2 imes 10^5)
对于数列中的所有数(a_i),均满足(-{10}^9 leq a_i leq {10}^9)
样例数据说明:
(n=5),数列长度为(5),数列从第一项开始依次为([25957,6405,15770,26287,26465])
第一次查询为([2,2])区间内的第一小值,即为(6405)
第二次查询为([3,4])区间内的第一小值,即为(15770)
第三次查询为([4,5])区间内的第一小值,即为(26287)
第四次查询为([1,2])区间内的第二小值,即为(25957)
第五次查询为([4,4])区间内的第一小值,即为(26287)
题解
题意:给一个序列有(m)个询问,求区间第(k)大。
既然这是一道主席树板题,那么我就详细讲一下主席树吧。(当然还有其他方法过此题,但我也就只会打主席树了)
众所周知,主席树是一种奇怪的线段树,结合了前缀和思想,那么我们考虑这样一种想法。
我们用(n)棵线段树,第(i)棵线段树表示队列(1)~(i)的信息。
我们用节点的(value)表示(1)~(i)中值在区间内的数的个数。
但是这样的话区间就会很大,那么我们离散化一下就好了。
那么我们就会得到(n)棵形状相同的树:(画画不是很好,尽请谅解)
然后我们考虑一下如果第(i)棵树减去第(j)棵树(两棵树对应节点的(value)相减),那么我们得到的就是(i+1)~(j)这段区间的线段树了。
所以我们每次新建一棵树的时候就要记录一下这棵树的根节点。
这样我们就能在(O(logn))的时间复杂度下完成每个询问了,具体操作如下(感觉和平衡树求第(k)大差不多):
从根节点开始搜索,如果(k)小于等于左子树的(value),那么第(k)大一定在左子树的区间内,往左边搜,
否则的话就往右边搜,然后把(k)的值减去左子树的(value),
当搜到区间内只有一个数(也就是区间的(left==right)),那么这个数就是第(k)大的数了。
个人认为理解了主席树的思想后实现就比较简单了。
上代码:
#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[200009],to[200009],ans[200009];
struct bb{
int s,x;
}b[200009];
int rt[200009];
int l,r,k;
struct aa{
int l,r;
int s;
int cd[2];
}p[5000009];
int len;
bool cmp(bb x,bb y){return x.s<y.s;}
void init(int u,int l,int r){
p[u].l=l;p[u].r=r;
p[u].s=0;
if(l==r) return;
int mid=(l+r)/2;
if(l<=mid) init(p[u].cd[0]=++len,l,mid);
if(mid+1<=r) init(p[u].cd[1]=++len,mid+1,r);
}
void dfs(int u,int x){
p[u].s++;
if(p[u].l==p[u].r) return;
int mid=(p[u].l+p[u].r)/2;
len++;
if(x<=mid){
p[len]=p[p[u].cd[0]];
p[u].cd[0]=len;
}else{
p[len]=p[p[u].cd[1]];
p[u].cd[1]=len;
}
dfs(len,x);
}
void ask(int lu,int ru,int k){
if(p[lu].l==p[lu].r){
printf("%d
",ans[p[lu].l]);
return;
}
if(k<=p[p[ru].cd[0]].s-p[p[lu].cd[0]].s) ask(p[lu].cd[0],p[ru].cd[0],k);
else ask(p[lu].cd[1],p[ru].cd[1],k-(p[p[ru].cd[0]].s-p[p[lu].cd[0]].s));
}
int kk;
int main(){
scanf("%d%d",&n,&m);
for(int j=1;j<=n;j++){
scanf("%d",&a[j]);
b[j].s=a[j];
b[j].x=j;
}
sort(b+1,b+n+1,cmp);
for(int j=1;j<=n;j++){
if(j!=1 && b[j].x==b[j-1].x) k++;
to[b[j].x]=j-k;
ans[j-k]=b[j].s;
}
init(rt[0]=len=1,1,n-k);
for(int j=1;j<=n;j++){
rt[j]=++len;
p[len]=p[rt[j-1]];
dfs(len,to[j]);
}
for(int j=1;j<=m;j++){
scanf("%d%d%d",&l,&r,&k);
ask(rt[l-1],rt[r],k);
}
return 0;
}