关于可持久化线段树的入门,点这里;
这是个非常经典的主席树入门题——静态区间第 kk 小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定 nn 个整数构成的序列,将对于指定的闭区间查询其区间内的第 kk 小值。
输入格式
第一行包含两个正整数 n,mn,m,分别表示序列的长度和查询的个数。
第二行包含 nn 个整数,表示这个序列各项的数字。
接下来 mm 行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第 kk 小值。
输出格式
输出包含 mm 行,每行一个整数,依次表示每一次查询的结果
输入输出样例
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\%20% 的数据满足:1 leq n,m leq 101≤n,m≤10
对于 50\%50% 的数据满足:1 leq n,m leq 10^31≤n,m≤103
对于 80\%80% 的数据满足:1 leq n,m leq 10^51≤n,m≤105
对于 100\%100% 的数据满足:1 leq n,m leq 2 imes 10^51≤n,m≤2×105
对于数列中的所有数 a_iai,均满足 -{10}^9 leq a_i leq {10}^9−109≤ai≤109
样例数据说明:
n=5n=5,数列长度为 55,数列从第一项开始依次为[25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]
第一次查询为[2, 2][2,2]区间内的第一小值,即为 6405
第二次查询为 [3, 4][3,4] 区间内的第一小值,即为 15770
第三次查询为 [4, 5][4,5] 区间内的第一小值,即为 26287
第四次查询为 [1, 2][1,2] 区间内的第二小值,即为 25957
第五次查询为 [4, 4][4,4] 区间内的第一小值,即为 26287
找思路:
看完题目,我们可以想到:
level.1最朴素方法:将原序列存在一个数组里,对于每一个询问,我们都对其中的元素进行排序,之后查找。
很明显以上方法由于太过朴ruo素zhi,复杂度炸的死死的。
level.2权值线段树做法:
什么是权值线段树?
简单来说,权值线段树维护一列数中数的个数,是线段树的一种变形。对于每一个节点,我们可以用它来维护一个区间内有多少个值为k的数。
举个例子:
我们有数列1 2 4 3 2 1 4
数一数其中有2个1,2个2,1个3,2个4
于是我们可以建立这样的一个权值线段树:
每一个节点代表的区间是取值范围,节点的权值代表在节点表示范围内有多少个数。
理解权值线段树后,我们考虑这样一个思路:
1.因为题目数据跨范围过大,我们不得不将其离散化,离散化后我们便获得了数与排名的对应关系,以及一共有多少个大小不同的数,记不同大小的数的总数为num,记a[i]为原数列第i个数是第几小。
2.一个数一个数考虑,对于离散后的数列a,我们从前往后扫,每扫到一个数,我们都建立一棵权值线段树,树上记录的信息是:数列a上从编号1到编号i这一个数列。
比如,现在我们i等于3,前三个数分别是1 2 4(排名第一小,第二小,第四小)有1个1,1个2,1个4,我们建立一棵权值线段树:
长这样。
然后i等于7时,数列1 2 4 3 2 1 4,为我们就建立了一棵上面例子的权值线段树:
...........
对于每一个a[i]我们亦是如此;
3.要获取[l,r]的元素信息,假如l=4,r=7,我们可以这样做:
用i=7时建立的线段树减去i=4时的线段树。具体相减方法是对应节点的权值相减。
按照上述相减方法进行操作后,我们就成功得到了这样的一棵权值线段树:
对于这棵权值线段树的信息解读:他表示一个数列,这个数列有1个1,1个2,1个3,1个4.
再看看原序列中[4,7]的信息:3 2 1 4
1个3,1个2,1个1,1个4。
完美获得区间信息。
4.我们最后要做的工作是获取区间第k小的数的值。对此,我们可以利用dfs查找:
以上面相减获得的权值线段树为例,我们要获取第3小。
从根节点开始,根节点的左儿子权值为2,比3小,说明我们要查找的元素在右子树里;
由于我们是从小往大数,但是在进入右儿子后,我们忽略了左子树上节点的个数(左子树都比我们要找的节点小),因此我们要减去左子树的size,然后继续查找:3-2=1;
左儿子大小为1,说明要找的元素在左子树里,直接进入左子树;
我们发现l==r==3,因此说明我们要找的元素找到了,是第3小的数。于是我们return,逆映射后输出即可。
level.3主席树优化:
很明显,上述做法可行,但是空间会炸掉。对于一堆线段树,我们自然要用主席树进行整合利用空间。套上主席树模板即可。在此不再赘述,了解主席树基本原理的去文章开头,想知道做法的看代码即可。
#include<cstdio> #include<cstring> #include<algorithm> #define N 200007 using namespace std; int read()//本人祖传快读 { int ans=0; char ch=getchar(),last=' '; while(ch<'0'||ch>'9')last=ch,ch=getchar(); while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); return last=='-'?-ans:ans; } int top,root[N],ys[N],a[N],n,m;//a[i]表示第i个元素为第几小,ys[i]表示第i小的元素值是多少 struct lshnode{//离散化用 int data,id; }q[N]; struct node{//朱希树用(雾) int siz,ls,rs; }tree[N*25]; bool cmp(lshnode a,lshnode b)//离散化cmp比较函数 { return a.data<b.data; } int build(int l,int r)//第一棵权值线段树,节点值均为空 { int now=++top; if(l==r) { return now; } int mid=(l+r)>>1; tree[now].ls=build(l,mid); tree[now].rs=build(mid+1,r); return now; } int update(int now,int l,int r,int x)//随着一个一个元素的考虑而建树 { int p=++top; tree[p]=tree[now]; tree[p].siz++;//新加入一个节点,一路上size都要加一 if(l==r) { return p; } int mid=(l+r)>>1; if(x<=mid) tree[p].ls=update(tree[now].ls,l,mid,x); else tree[p].rs=update(tree[now].rs,mid+1,r,x); return p; } int query(int u,int v,int l,int r,int x)//u,v从l-1,r来的 { if(l==r)return l;//找到第k小了 int delta=tree[tree[v].ls].siz-tree[tree[u].ls].siz;//只考虑左子树size即可 int mid=(l+r)>>1; if(x<=delta)//在左子树里面 return query(tree[u].ls,tree[v].ls,l,mid,x);//下一层查找 else return query(tree[u].rs,tree[v].rs,mid+1,r,x-delta); } int main(){ n=read(),m=read(); for(int i=1;i<=n;i++) q[i].data=read(),q[i].id=i; sort(q+1,q+1+n,cmp); //我们需要映射:第k小的数是多少,第k个数是第几小 q[0].data=1000000007;//鬼知道干什么hhh int num=0;//离散化后的 不同数 的个数 for(int i=1;i<=n;i++) { if(q[i].data!=q[i-1].data)num++; a[q[i].id]=num; ys[num]=q[i].data; }//以上离散化 root[0]=build(1,num); for(int i=1;i<=n;i++) root[i]=update(root[i-1],1,num,a[i]); for(int i=1,l,r,x;i<=m;i++) { l=read();r=read();x=read(); printf("%d ",ys[query(root[l-1],root[r],1,num,x)]); } return 0; }
完结撒花~