第k大的数(主席树模板)
描述
你为Macrohard公司的数据结构部门工作,你的工作是重新写一个数据结构,这个数据结构能快速地找到一段数列中第k大的数。
就是说,给定一个整数数列a[1..n],其中每个元素都不相同,你的程序要能回答一组格式为Q (i , j , k)的查询,Q(i, j ,k)的意思是“在a[i..j]中第k大的数是多少?”
例如令 a = {1, 5, 2, 6, 3, 7, 4},查询格式为Q (2 , 5 , 3),数列段a[2..5] = {5, 2, 6, 3},第3大的数是5,所以答案是5。
输入
文件第一行包括一个正整数n,代表数列的总长度,还有一个数m,代表有m个查询。 n,m满足:1≤n≤100 000, 1≤m≤5 000 第二行有n个数,代表数列的元素,所有数都不相同,而且不会超过109 接下来有m行,每行三个整数i , j , k,代表一次查询, i , j , k满足1≤i≤j≤n, 1≤k≤j − i + 1
输出
输出每个查询的答案,用换行符隔开
1 /*template of chair tree 2 lower_bound函数是用来返回一个值在单调递增数列里大于等于这个值的最小值 3 upper_bound函数实际上就是将lower_bound里面的大于等于改成了严格大于 4 这里p的作用仅仅是在记录这个点的编号,我们用一维数组来存储树,只是该指向哪个位置 5 同时我们输入了几个数就维护那么大的区间,不一定一定要从1枚举到最大值,比如说1,8,5,4 6 仅需要四个数组位置,我每往后加一个数字就要建一棵节约空间的线段树,类似于AC自动机的思想 7 那么这个数组对应的a数组就是 1,4,3,2,每加一个就相当于一个节点修改了值然后向上维护,不过这 8 里没有使用pushup函数,而是直接在二分的过程中进行累加,可以更好地维护值 9 为什么我们要维护这么多棵线段树,因为我们要像前缀和数组一样通过一个版本减去前面的版本 10 来获得所需区间的排序信息,再进行二分就可以了. 1112 */ 13 #include<bits/stdc++.h> 14 #define N 100005 15 using namespace std; 16 struct Node{ 17 int l,r,sum; 18 }T[N*40]; 19 int a[N],b[N],n,m,rt[N*40]; 20 int tot=0; 21 inline void build(int &p,int l,int r){//初始建图 22 p=++tot; 23 T[p].sum=0; 24 if(l==r)return; 25 int mid=(l+r)>>1; 26 build(T[p].l,l,mid); 27 build(T[p].r,mid+1,r); 28 } 29 inline void update(int &p,int l,int r,int o,int k){ 30 /*几个变量分别表示为当前节点的编号/左端点/右端 31 点/上一棵线段树/原数组该值对应的名次*/ 32 p=++tot;// 这个点的编号就是新开空间 ,这个空间对应的下标就是这个点,不是值 33 T[p].sum=T[o].sum+1;//由于是更新一个值,所以沿途走下去的链(要更新的点)中都要比上一个版本多1 34 T[p].l=T[o].l;// 35 T[p].r=T[o].r; 36 if(l==r)return;//如果已经到叶节点了,那么就代表更新结束 37 int mid=(l+r)>>1;//进行二分 38 if(k<=mid)update(T[p].l,l,mid,T[o].l,k); 39 else update(T[p].r,mid+1,r,T[o].r,k); 40 } 41 inline int query(int p1,int p2,int l,int r,int k){ 42 if(l==r)return l;//这里的l和r如果相同,就是指这个k大就是在这个节点,而且这个节点的下标(对应到b)会被返回 43 int t=T[T[p2].l].sum-T[T[p1].l].sum;//两棵线段树作差 44 int mid=(l+r)>>1;//进行二分 45 if(t>=k)return query(T[p1].l,T[p2].l,l,mid,k); 46 else return query(T[p1].r,T[p2].r,mid+1,r,k-t); 47 } 48 inline long long read(){ 49 long long ans=0,w=1; 50 char ch=getchar(); 51 while(!isdigit(ch)){ 52 if(ch=='-')w=-1; 53 ch=getchar(); 54 } 55 while(isdigit(ch)){ 56 ans=(ans<<3)+(ans<<1)+ch-'0'; 57 ch=getchar(); 58 } 59 return ans*w; 60 } 61 inline void write(long long x){ 62 if(x<0){ 63 putchar('-'); 64 x=-x; 65 } 66 if(x>9)write(x/10); 67 putchar(x%10+'0'); 68 } 69 int main(){ 70 n=read(); 71 m=read(); 72 for(int i=1;i<=n;++i){ 73 a[i]=read(); 74 b[i]=a[i];//复制数组用lower_bound函数查找位置 75 } 76 sort(b+1,b+n+1);//lower_bound函数必须是严格单调递增,配合unique函数 77 int siz=unique(b+1,b+n+1)-b-1;//进行去重操作 78 build(rt[0],1,siz);//对第0棵树进行建图 79 for(int i=1;i<=n;++i)a[i]=lower_bound(b+1,b+siz+1,a[i])-b; 80 /*找到a[i]该在的位置,此时a数组的作 81 用变为了记录对应数字应该在的下标 , 82 类似于一般我们是数组下标对应值,而 83 这里新加的操作就是原数列的每个值 84 应该对应到排序过后的第几个位置, 85 相当于第几名 86 */ 87 for(int i=1;i<=n;++i)update(rt[i],1,siz,rt[i-1],a[i]); 88 while(m--){ 89 int ql=read(),qr=read(),k=read(); 90 write(b[query(rt[ql-1],rt[qr],1,siz,k)]); 91 puts(""); 92 } 93 return 0; 94 }