第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

输出

输出每个查询的答案,用换行符隔开

样例输入[复制]
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
样例输出[复制]
5
6
3
 
 
 
 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 }

 

posted @ 2018-08-28 15:40  saionjisekai  阅读(238)  评论(0编辑  收藏  举报