POJ - 2104
主席树和值域线段树存的值都是一样的,但是主席树是n棵线段树,第i棵线段树存储的是i和i之前的所有信息(和前缀和类似),所以在求有关区间L~R的时候就可以用第R棵线段树减去第L-1棵线段树对应节点的值就得到了L~R之间的线段树。
类似值域线段树,没次插入一个数的时候最多修改一条树链,所以我们在给每个节点都建议可线段树的时候,它和前一棵线段树的差别也只有一条链,所以当我们在建第i棵线段树的时候每当遇到一个节点,只需把第i-1棵线段树的相应节点的信息copy过来然后再根据需要修改的链来改变该节点某一个子节点的指向,而另一个就和第i-1棵线段树共用一个节点,这样就大大的节省了空间,每新建一棵线段树我们需要新开的节点数最多是log(数据范围)(还可以离散优化)。
区间的第k小就是根据主席树得到这个区间的线段树之后再查询在这可树上查询,返回结果是要求的值,离散后返回的值是离散的下标。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=5e6+10;
const int inf=0x3f3f3f3f;
int n,m;
struct AC
{
struct zp
{
int l,r,sum;
} tree[maxn];
int cont;
void init()
{
cont=1;
tree[0].l=tree[0].r=tree[0].sum=0;//第0棵线段树每一个节点的值都是0所以可以用一个点来表示使其不断循环形成第0棵线段树
}
/*
每新建一棵线段树相比于前面的那棵线段树更改的只会是一条树链,其他的节点都可以共用
新建节点,并且将前一棵线段树的节点值复制过来,相应的数量++,并更改上一个节点的指向,这里用的是引用变量
*/
void update(int pre,int &k,int l,int r,int num,int val)//
{
//
tree[cont]=tree[pre],tree[cont].sum+=val,k=cont++;
if(l==r) return ;//遇到根节点返回
int mid=(l+r)>>1;//继续往下递归
if(num<=mid)
update(tree[pre].l,tree[k].l,l,mid,num,val);
else
update(tree[pre].r,tree[k].r,mid+1,r,num,val);
}
/*
查询ql+1~qr区间第k小,用第qr可线段树和第ql棵线段树相应节点sum值作差的到一棵新的线段树
这棵线段树代表的就是这个区间的线段树,在上面查找第k大就好
*/
int query(int k,int l,int r,int ql,int qr)//返回的是离散后的数组下标
{
if(l==r) return l;
int sum=tree[tree[qr].l].sum-tree[tree[ql].l].sum;//求出左节点的值大小
int mid=(l+r)>>1;
if(sum>=k)//在左节点
return query(k,l,mid,tree[ql].l,tree[qr].l);
else//在右节点
return query(k-sum,mid+1,r,tree[ql].r,tree[qr].r);
}
} ac;
int x[maxn],root[maxn],Hash[maxn];
int get_hash(int x)//值太大的话就要选择离散
{
return lower_bound(Hash+1,Hash+1+n,x)-Hash;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
ac.init();
for(int i=1; i<=n; i++)
scanf("%d",&x[i]),Hash[i]=x[i];
sort(Hash+1,Hash+n+1);//离散
for(int i=1; i<=n; i++)//每一个节点新建一棵线段树
ac.update(root[i-1],root[i],1,n,get_hash(x[i]),1);
while(m--)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
printf("%d\n",Hash[ac.query(c,1,n,root[a-1],root[b])]);
}
}
}