主席树是一个很神奇的东西,效率次于划分树。
关于思路,还是用图片来展示吧:
附上poj2104代码:
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<cmath>
#define M 100000+5
using namespace std;
/*
以poj2104求[l,r]第k大的数为例探究主席树
*/
struct node
{
int l,r,size;
node(){l=r=size=0;
}
}T[20*M];
int root[M];//记录第i个子树的根
int tot;//各点标号
int m;
int a[M];//读入数组
int t[M];//离散数组 1,5,7,9等数字离散后 t[1]=1 t[2]=5 t[3]=7
int pos(int x)
{
return lower_bound(t+1,t+m+1,x)-t;//寻找在离散数组中的位置
/*int l=1,r=m;
while(l<r)
{
int mid=(l+r)/2;
if(x<=t[mid])r=mid;
else l=mid+1;
}
return l;*/
}
int n;//n个数
int q;//q个询问
bool cmp(int x,int y)
{
return x<y;
}
int build(int l,int r)
{
int rt=tot++;
T[rt].size=0;
if(l==r)return rt;
int mid=(l+r)/2;
T[rt].l=build(l,mid);
T[rt].r=build(mid+1,r);
return rt;
}//建树
int updata(int u,int l,int r,int x,int v)/*新建节点和u
进行比较(同一级),在区间[l,r]中把x的位置的size加上v
*/
{
int rt=tot++;
T[rt]=T[u];//信息传递
T[rt].size+=v;//加上size
//要注意的是构造新节点的时候我们是从上往下构造的,
//而不是回溯的时候
if(l==r)return rt;
int mid=(l+r)/2;
if(x<=mid)//要修改的信息在左儿子
{
T[rt].l=updata(T[u].l,l,mid,x,v);
}
else T[rt].r=updata(T[u].r,mid+1,r,x,v);
return rt;
}
int query(int i,int j,int l,int r,int k)//i,j两棵树对比
{
if(l==r)return l;
int cha=T[T[j].l].size-T[T[i].l].size;
int mid=(l+r)/2;
if(cha>=k)return query(T[i].l,T[j].l,l,mid,k);
else return query(T[i].r,T[j].r,mid+1,r,k-cha);
}
int main()
{
while(scanf("%d%d",&n,&q)!=EOF)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
t[i]=a[i];
}
sort(t+1,t+n+1,cmp);
m=unique(t+1,t+n+1)-t-1;
m++;t[m]=0x3f3f3f3f;
tot=0;
root[0]=build(1,m);//初始化树
for(int i=1;i<=n;i++)//每个点的建树通过Logn的上传修改
{
int k=pos(a[i]);//k是a[i]这个数对应在离散后的数中的位置
root[i]=updata(root[i-1],1,m,k,1);//传递修改 ,参数含义见上
}
//处理好之后剩下的是查询
for(int i=1;i<=q;i++)
{
int l,r,k;//寻找[l,r]区间里的第k小的元素
scanf("%d%d%d",&l,&r,&k);
printf("%d\n",t[query(root[l-1],root[r],1,m,k)]);//查询[l,r]里面第k小的元素
}
}
return 0;
}