这里讲静态的主席树,关于静态区间第k小。(有兴趣的朋友还可以去看看我写的整体二分,代码实现略优于主席树我觉得,当然静态主席树是很好写的)
题目描述:
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第k小值。
输出格式:
输出包含k行,每行1个正整数,依次表示每一次查询的结果
输入输出样例
6405 15770 26287 25957 26287
那么我们明确主席树是个什么东西。它就是,可持久化线段树。
首先我们不考虑主席树,而是对于这个区间第k小问题做一个分析。如果我们对每一个区间暴力快排,毋庸置疑,绝对炸上天,T到你想哭。
于是聪明的前人发现了这个问题的一个特点,下面我来说说。
先把每一个数值离散化,树中每一个值就即是权值也是排名了(离散化建议看看我之前写的离散化博客里的代码)。好吧我到现在还没有说明这个树到底是个什么玩意儿。
发明者的原话:“对于原序列的每一个前缀[1···i]建立出一棵线段树维护值域上每个数出现的次数,则其树是可减的”
可以加减的理由:主席树的每个节点保存的是一颗线段树,维护的区间信息,结构相同,因此具有可加减性(关键)
是的,我们对于每一个前缀建一颗树,每一段区间维护的是当前区间的点的个数,注意这里的区间不是位置的区间,而是权值的区间,
这就是为什么要离散化了。
那么减可以干什么呢?注意这是个很重要的思想,前缀和。
先考虑一个问题,如果每次询问的l都是数组一开头的位置1,是不是很容易维护?
下面举一个例子:维护数组6 2 3 1 4 5。 对前缀1~6建树 (图难看。。。不打紧的咳咳),这个求第k小应该一目了然吧
然而,区间第k小其实很容易,对于区间[l,r]而言,只需要在每个节点用前缀r的节点值减去前缀l-1的节点值就好了,其他的和上面是一样的
下面附上一组大佬的图
注意和我的数据是不一样的,他的是4 1 1 2 8 9 4 4 3
然后读者按照我刚刚说的用前缀r的树的每一个节点减去对应的前缀l-1的树的节点得到一颗新的树就好
好的,现在我们回到原来的问题,主席树。其实上面就是主席树,但是若是我们对于每一个前缀都暴力建一次树的话,在时间和空间上都不能接受。
这时候我们注意到相邻的两棵树其实是很相似的,我们可以让这一棵树和上一棵树共用一些节点,从而达到减低空间和时间复杂度的效果。前缀每次向右一位,其实就是多插入了一个数值,新的
树和上一棵树的唯一区别其实就是一条链上变了而已。
事实证明,这种方法十分的有效。
就像这样,在原来的基础上加上几个点
下面我附上我的代码供大家研究,其实我认为代码更加好懂
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+15; int n,m,cnt; int a[maxn],b[maxn],tree[maxn<<5],L[maxn<<5],R[maxn<<5],sum[maxn<<5]; int build(int l,int r) { int rt=++cnt; sum[rt]=0; if (l<r) { int mid=(l+r)>>1; L[rt]=build(l,mid); R[rt]=build(mid+1,r); } return rt; } int update(int pre,int l,int r,int x) { int rt=++cnt; L[rt]=L[pre];R[rt]=R[pre];sum[rt]=sum[pre]+1;//多插入了点于是加个1 if (l<r) { int mid=(l+r)>>1; if (x<=mid) L[rt]=update(L[pre],l,mid,x);//看看插到哪一边,另一边其实是一样的 else R[rt]=update(R[pre],mid+1,r,x); } return rt; } int query(int u,int v,int l,int r,int k) { if (l>=r) return l; int x=sum[L[v]]-sum[L[u]];//减一下就好 int mid=(l+r)>>1; if (x>=k) return query(L[u],L[v],l,mid,k); else return query(R[u],R[v],mid+1,r,k-x); } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) { scanf("%d",&a[i]); b[i]=a[i]; } sort(b+1,b+1+n);//离散化 int q=unique(b+1,b+1+n)-b-1; tree[0]=build(1,q); for (int i=1;i<=n;i++) { int t=lower_bound(b+1,b+1+q,a[i])-b; tree[i]=update(tree[i-1],1,q,t);//通过上一个树建树 } while (m--) { int x,y,z; scanf("%d%d%d",&x,&y,&z); int t=query(tree[x-1],tree[y],1,q,z);//通过x-1树和y树相减 printf("%d ",b[t]); } return 0; }
上文部分图来自大佬 Lpy_Now,感谢大佬