求区间第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;
}