poj-2104 K-th Number[主席树/函数式线段树/可持久化线段树]

来源:互联网 发布:单片机可靠性 编辑:程序博客网 时间:2024/06/05 01:52

膜拜大神:点击打开链接点击打开链接

【题目描述】有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]数字的值域的区间出现次数。

举个栗子:

410 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:busing 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;}

0 0
原创粉丝点击