膜拜大神:点击打开链接 点击打开链接
【题目描述】有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; }
//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; }
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; }