【题目描述】有n个数字排成一列,有m个询问,格式为:left right k .即问在区间[left,right]第k大的数据为多少?
纯属个人理解,有不正确的地方欢迎留言指正:
先来设想下如何解决这个问题。
把数字在数组中的位置i作为定义域,数字的值v[i]作为值域。
假如对于 [left,right]的数我们能知道它们的值域在不同区间出现的个数,就可以根据出现个数来二分查找来找的第k大的值,而区间出现的个数可以用线段树来存储。
为了避免浪费空间,将值域离散化存进线段树。对于某段值域[l,r],[left,right]中数字出现的个数 =[0,right]中数字出现的个数-[0,left-1]中数字出现的个数
那么就可以建立 n+1颗线段树,第i颗线段树记录[0,i]数字的值域的区间出现次数。
举个栗子:
4 10 31 42 15这个数据按照上面思路可以建立5个线段树
很明显,时间效率和空间效率都很高。
从图上也能很直观的看出每个线段树之间有很多可以共用的节点。
可持久化数据结构
在算法执行的过程中,会发现在更新一个动态集合时,需要维护其过去的版本。这样的集合称为是可持久的。
实现持久集合的一种方法时每当该集合被修改时,就将其整个的复制下来,但是这种方法会降低执行速度并占用过多的空间。
可持久化数据结构(Persistent data structure)就是利用函数式编程的思想使其支持询问历史版本、同时充分利用它们之间的共同数据来减少时间和空间消耗。
单点更新
第i个树建立的时候,是在第i-1个树的基础上把某条从树根到叶子节点的路径上的节点+1。这条路径上的节点是必须新建的,非此路径的节点可以与上颗树共用的,与它共用。
这明显只能用链式结构,所以不能对线段树用标号实现了。
结构
struct node{
node *ls,*rs;
int cnt;
};
第0颗树就只能单独建立了
node *build(int l,int r)
{//构建第0个线段树
node *rt=get_nd();
if(l==r){
rt->cnt=0,rt->ls=rt->rs=NULL;
return rt;
}
int mid=(l+r)>>1;
rt->ls=build(l,mid);
rt->rs=build(mid+1,r);/
push_up(rt);
return rt;
}
构建第1颗到第n颗线段树
//pos为第i个数的离散化后的值域,val=1表示增加的个数
//pre是第i-1颗线段树的指针
node *update(node *pre,int l,int r,int pos,int val)
{//在前一个线段树的基础上构建此次线段树
node *rt=get_nd();
*rt=*pre;
if(l==pos&&r==pos){
rt->cnt+=val;
return rt;
}
int mid=(l+r)>>1;
if(pos<=mid) rt->ls=update(pre->ls,l,mid,pos,val);
else rt->rs=update(pre->rs,mid+1,r,pos,val);
push_up(rt);
return rt;
}
查询第k小个值
int query(node *cur,node *pre,int l,int r,int kth)
{//查询
if(l==r) return l;
int mid=(l+r)>>1;
int lim=cur->ls->cnt-pre->ls->cnt;
if(kth<=lim) return query(cur->ls,pre->ls,l,mid,kth);
else return query(cur->rs,pre->rs,mid+1,r,kth-lim);
}
poj2104 代码:
/*【题目描述】有n个数字排成一列,有m个询问,格式为:left right k
即问在区间[left,right]第k大的数据为多少?*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define find_max(a,b) a>b?a:b
using namespace std;
const int maxn=100008;
struct node{
node *ls,*rs;
int cnt;
}rsta[maxn*30];//内存池
int scnt,n,m;
node *rot[maxn];//第i个线段树的根节点
int v[maxn];//每个数字的原值
int dv[maxn];//每个数字的离散值
int sub_v[maxn];//v排序后的备份
int maxv;//最大的离散值
inline node *get_nd()
{//从预开的内存池中得到一个节点
return &rsta[scnt++];
}
inline void push_up(node *rt)
{//上压
rt->cnt=rt->ls->cnt+rt->rs->cnt;
}
node *build(int l,int r)
{//构建第0个线段树
node *rt=get_nd();
if(l==r){
rt->cnt=0,rt->ls=rt->rs=NULL;
return rt;
}
int mid=(l+r)>>1;
rt->ls=build(l,mid);
rt->rs=build(mid+1,r);/
push_up(rt);
return rt;
}
node *update(node *pre,int l,int r,int pos,int val)
{//在前一个线段树的基础上构建此次线段树
node *rt=get_nd();
*rt=*pre;
if(l==pos&&r==pos){
rt->cnt+=val;
return rt;
}
int mid=(l+r)>>1;
if(pos<=mid) rt->ls=update(pre->ls,l,mid,pos,val);
else rt->rs=update(pre->rs,mid+1,r,pos,val);
push_up(rt);
return rt;
}
int query(node *cur,node *pre,int l,int r,int kth)
{//查询
if(l==r) return l;
int mid=(l+r)>>1;
int lim=cur->ls->cnt-pre->ls->cnt;
if(kth<=lim) return query(cur->ls,pre->ls,l,mid,kth);
else return query(cur->rs,pre->rs,mid+1,r,kth-lim);
}
void discretize()
{//离散化
for(int i=1;i<=n;++i)
sub_v[i]=v[i];
sort(sub_v+1,sub_v+n+1);
int size=unique(sub_v+1,sub_v+n+1)-sub_v-1;//size为离散化后元素个数
for(int i=1;i<=n;++i)//k为b[i]经离散化后对应的值
{
dv[i]=lower_bound(sub_v+1,sub_v+size+1,v[i])-sub_v;
maxv=find_max(dv[i],maxv);
}
}
void solve()
{
scnt=0,maxv=0;
discretize();
int t=maxv,r=1;
while(t) t/=2,r*=2;//求值域范围
rot[0]=build(1,r);
for(int i=1;i<=n;++i)
rot[i]=update(rot[i-1],1,r,dv[i],1);
int x,y,k;
while(m--)
{
scanf("%d%d%d",&x,&y,&k);
k=query(rot[y],rot[x-1],1,r,k);//得到的是离散值
printf("%d\n",sub_v[k]);
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=1;i<=n;++i)
scanf("%d",v+i);
solve();
}
return 0;
}