求区间第k大数是多少
用我惯用的线段树写法似乎不适合写主席树,看别人的代码好半天才看懂
用root表示每一个前缀树的根,思想大致是由第i-1个树构成的树建第i个树,由于加入一个数只需要log级别修改,所以建树效率很高。
主席树的精髓在于可持续化,就是说之前区间的信息不去修改它,递推加入新元素的时候,要新开log个空间来存储。因为主席树本身比较占用空间,只需改变这些空间的指向就可以重复利用不变的空间,达到节省空间的目的。
简而言之,主席树是一种重复利用数据不变的空间,建成空间上只有一颗完整的树,但逻辑上是多颗树同时共存的数据结构。
他具有线段树的查询效率。
更大的好处是,在处理某些问题上更符合人类的思维,树的同层之间有很好的性质。
#include<iostream> #include<cstdio> #include<cstdlib> #include<string.h> #include<math.h> #include<algorithm> #include<vector> #include<queue> #include<map> using namespace std; struct node { int id,val; }s[100005]; int b[100005],cnt,root[100005]; bool cmp(const node &a,const node &b) { return a.val<b.val; } struct node1 { int l,r,val; }tree[100005*20]; int build(int l,int r) { cnt++; int now=cnt; tree[now].val=0; if(l==r) return now; int mid=(l+r)>>1; tree[now].l=build(l,mid); tree[now].r=build(mid+1,r); return now; } void up(int now) { tree[now].val=(tree[tree[now].l].val+tree[tree[now].r].val); } int update(int ori,int l,int r,int x) { cnt++; int now=cnt; tree[now]=tree[ori]; if(l==r) { tree[now].val++; return now; } int mid=(l+r)>>1; if(x<=mid) tree[now].l=update(tree[ori].l,l,mid,x); else tree[now].r=update(tree[ori].r,mid+1,r,x); up(now); return now; } int query(int l,int r,int ox,int oy,int k) { if(l==r) return l; int res = tree[tree[oy].l].val- tree[tree[ox].l].val; int m = (l+r)/2; if(k <= res) return query(l,m,tree[ox].l,tree[oy].l,k); else return query(m+1,r,tree[ox].r,tree[oy].r,k-res); } int main() { int n,q; while(cin>>n>>q) { for(int i=1;i<=n;i++) { scanf("%d",&s[i].val); s[i].id=i; } sort(s+1,s+1+n,cmp); for(int i=1;i<=n;i++) b[s[i].id]=i; cnt=0; root[0]=build(1,n); for(int i=1;i<=n;i++) { root[i]=update(root[i-1],1,n,b[i]); } while(q--) { int a,b,c; scanf("%d%d%d",&a,&b,&c); int id=query(1,n,root[a-1],root[b],c); printf("%d\n",s[id].val); } } return 0; }
原文地址:http://blog.csdn.net/accepthjp/article/details/41146333