主席树(区间第k小)
Posted water-radish
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了主席树(区间第k小)相关的知识,希望对你有一定的参考价值。
//主席树 权值线段树+可持久化 //权值线段树:在此处指各个数字在某个区间内出现的次数 //那么第一棵权值线段树会记录[1,1]的数字出现次数 //第n棵权值线段树会记录[1,n]的数字出现次数 //例:数列为110001 //第一棵权值线段树记录为tree1[0]=0 tree1[1]=1 //第二棵权值线段树记录为tree2[0]=0 tree2[1]=2 //第六棵权值线段树记录为tree6[0]=3 tree6[1]=3 //那么要求区间为[3,5]的数字出现次数可拿第五棵权值线段树减去第(三减一)棵权值线段树 //此处运用前缀和思想 //将多棵权值线段树的公共点合为一个点可减少空间复杂度 //求区间第k小即可求出区间数字出现次数后递归操作 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; int n,m,cnt,b[200001],root[200001];//b[]离散化后的值 root[]根的编号 struct uio{ int num,id; }a[200001];//离散化辅助结构体 struct rty{ int ls,rs,siz;//左儿子,右儿子,区间元素个数 }tree[5000001];//权值线段树 递增使得左儿子表示的值小于右儿子表示的值 bool cmp(uio x,uio y) { return x.num<y.num; } void update(int l,int r,int k,int &now)//新建节点的左右儿子,新建节点值,当前节点 { tree[++cnt]=tree[now]; now=cnt; tree[now].siz++; if(l==r) return; int mid=(l+r)/2; if(k<=mid)//新建节点在左子树 update(l,mid,k,tree[now].ls); else update(mid+1,r,k,tree[now].rs);//新建节点在右子树 } int query(int l,int r,int x,int y,int k)//左右儿子,两棵权值线段树编号,第k大 { if(l==r) return l; int dif=tree[tree[y].ls].siz-tree[tree[x].ls].siz;//见本代码第9行 int mid=(l+r)/2; if(k<=dif)//左儿子的出现次数已大于等于k 说明k在左子树 return query(l,mid,tree[x].ls,tree[y].ls,k); else return query(mid+1,r,tree[x].rs,tree[y].rs,k-dif);//k在右子树 //k不在左子树 但是左子树代表的数的出现次数包含在k中 因此要减去(同平衡树) } void do_something() { for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d%d",&u,&v,&w); printf("%d ",a[query(1,n,root[u-1],root[v],w)].num); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i].num); a[i].id=i; } sort(a+1,a+1+n,cmp); for(int i=1;i<=n;i++) b[a[i].id]=i;//离散化 for(int i=1;i<=n;i++) { root[i]=root[i-1];//暂时将新的根节点赋为原根节点的编号 update(1,n,b[i],root[i]);//新建一棵权值线段树 } do_something(); return 0; }
以上是关于主席树(区间第k小)的主要内容,如果未能解决你的问题,请参考以下文章
少年,想学带修改主席树吗 | BZOJ1901 带修改区间第k小