【洛谷P3834】(模板)可持久化线段树 1(主席树)
(模板)可持久化线段树 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\times 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\n",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;
}