作用
用于查询区间第k小值等,本质是线段树,因为是某个主席发明而得名,算法内容与主席无关.
实现方法
线段树维护序列的值的个数,并对每一个前缀建一棵线段树(主席树不是这样的,否则空间…),那么要查区间[l,r]就只需要将r这棵树减去(l-1)这棵线段树,在相减后的线段树查找即可(实际不需要再建一棵树).
空间优化:根据上述方法,可以发现相邻两棵线段树的区别只有log,因而后面的线段树可以与前面的线段树共用节点,总空间复杂度为O(n*log^2).
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#define N 200100
using namespace std;
int n,m,num[N],b[N],c[N],cc,tt,root[N];
struct Tree
{
int l,r,gs,l_son,r_son;
}tree[N*50];
map<int,int>id;
void build(int now,int u,int v)
{
tree[now].l=u;
tree[now].r=v;
tree[now].gs=0;
if(u!=v)
{
int mid=(u+v)/2;
tt++;
tree[now].l_son=tt;
build(tt,u,mid);
tt++;
tree[now].r_son=tt;
build(tt,mid+1,v);
}
else
{
tree[now].l_son=tree[now].r_son=-1;
}
}
void add(int now,int u)
{
if(tree[now].l_son!=-1&&u<=tree[tree[now].l_son].r)
{
tt++;
tree[tt]=tree[tree[now].l_son];
tree[tt].gs++;
tree[now].l_son=tt;
add(tt,u);
return;
}
else if(tree[now].r_son!=-1&&u>tree[tree[now].l_son].r)
{
tt++;
tree[tt]=tree[tree[now].r_son];
tree[tt].gs++;
tree[now].r_son=tt;
add(tt,u);
return;
}
return;
}
int ask(int u,int v,int w)
{
if(tree[u].l==tree[u].r)
return c[tree[u].l];
if(w<=tree[tree[u].l_son].gs-tree[tree[v].l_son].gs)
return ask(tree[u].l_son,tree[v].l_son,w);
return ask(tree[u].r_son,tree[v].r_son,w-(tree[tree[u].l_son].gs-tree[tree[v].l_son].gs));
}
int main()
{
int i,j,o,p,q;
cin>>n>>m;
for(i=1;i<=n;i++)
{
scanf("%d",&num[i]);
b[i]=num[i];
}
sort(b+1,b+n+1);
for(i=1;i<=n;i++)
{
if(b[i]!=c[cc]||!cc)
{
cc++;
c[cc]=b[i];
id[b[i]]=cc;
}
}
root[0]=1;
tt++;
build(1,1,cc);
for(i=1;i<=n;i++)
{
tt++;
root[i]=tt;
tree[root[i]]=tree[root[i-1]];
tree[root[i]].gs++;
add(root[i],id[num[i]]);
}
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&p,&q,&o);
printf("%d\n",ask(root[q],root[p-1],o));
}
}