可持久化线段树-主席树
主席树又称函数式线段树,顾名思义,就是通过函数实现的,也称可持久化线段树,支持在线处理,可以访问历史版本,后一刻可参考前一刻的状态,充分利用共同数据,
节省空间;主席树可以实现查询区间k小的问题等;
主席树的每个节点对应一棵线段树,对于一个序列,我们采用前缀和的思想,如a[1],a[2],a[3],.....,每个前缀建一棵线段树,那么就共有N棵线段树,两棵线段树之差得到的就是
某个区间数字出现的次数,插入一个值时,相当于新建一棵线段树,在前一棵线段树的基础上,新建logn个节点,其余的从前一棵线段树那里copy过来,线段树里维护的是在
这n个数的序列中,排名第K的数出现的次数,若要处理区间[l,r]问题,只需要利用前缀和思想,rt[r]-rt[l-1]就好了,rt[l-1]处理的是区间1-(l-1),rt[r]处理的是区间1-r,所以rt[r]-rt[l-1]处理的
就是区间l-r;
更新
首先需要建立一棵空线段树,rt数组里存的是第i棵线段树的根节点的编号,当我们插入一个值时,只需要修改从该节点到根的一条路径,若该节点进入线段树右子树,则左子树可以共用,若进入左子树,则右子树可以共用,直到到达叶子节点;
查询 区间第k[L,R]
思想为二分法,定义num=rt[now].val-rt[cmp].val,当k<=num时,直接往左子树查找,否则在右子树中查找,但注意num要减k,因为左子树肯定小于它了,直到L==R;
luogu 3834:
#include<cstdio> #include<iostream> #include<algorithm> using namespace std; #define maxn 200009 int cnt=0,a[maxn],s[maxn],ls[maxn*20],rs[maxn*20],val[maxn*20],rt[maxn],sz; void build(int &p,int l,int r){ p=++cnt; if(l!=r){ int mid=(l+r)/2; build(ls[p],l,mid); build(rs[p],mid+1,r); } } void insert(int &p,int cmp,int x,int l,int r){ p=++cnt;ls[p]=ls[cmp];rs[p]=rs[cmp];val[p]=val[cmp]+1; if(l!=r){ int mid=(l+r)/2; if(x<=mid){ insert(ls[p],ls[cmp],x,l,mid); } else{ insert(rs[p],rs[cmp],x,mid+1,r); } } } int query(int l,int r,int L,int R,int k){ if(L==R) return L; int num=val[ls[r]]-val[ls[l]],mid=(L+R)/2; if(k<=num) return query(ls[l],ls[r],L,mid,k); else return query(rs[l],rs[r],mid+1,R,k-num); } int main() { int n,m,l,r,k,i; scanf("%d%d",&n,&m); for(i=1;i<=n;i++){ scanf("%d",&a[i]); s[i]=a[i]; } sort(s+1,s+n+1); sz=unique(s+1,s+n+1)-s-1; build(rt[0],1,sz); for(i=1;i<=n;i++){ insert(rt[i],rt[i-1],lower_bound(s+1,s+sz+1,a[i])-s,1,sz); } for(i=1;i<=m;i++){ scanf("%d%d%d",&l,&r,&k); printf("%d ",s[query(rt[l-1],rt[r],1,sz,k)]); } }