主席树/函数式线段树/可持续化线段树 POJ_2104,BZOJ_1901,ZOJ_2112

来源:互联网 发布:js数组的sort 编辑:程序博客网 时间:2024/05/30 23:37

明天就要去上海了,我还这么狗,感觉自己萌萌哒。


    这几天学主席树,熬夜了三次,每次都到三四点,但是都不困,已然爱上深夜打代码。

    主席树是一种神奇的数据结构,它将线段树和前缀和结合起来,维护一个一维数组的数值分布信息(数值经过离散化处理)。可以通过主席树快速查询数组某位置l到另一位置r之间元素的有序信息(比如第k大的数字是多少,比某个数值小的元素有多少)。

    实现方法是构造多棵线段树,每棵线段树维护数组中一段的元素有序信息,第i棵线段树维护从头到数组i位置的元素有序信息。由于每棵线段树在理论上是完全相同的(实际实现时存在空节点),即树a的每一个点,都可以在主席树中的另一棵树b上找到对应节点,因此线段树之间可以进行加减操作(加减操作根据具体求的信息,基于递归和点与点的比较实现,详见例题)。比如我们想知道l到r位置的某个信息,主席树建树后,第l-1棵线段树存储了从头到l-1位置的信息,第r棵线段树存储了从头到r位置的信息,只需要用第r棵线段树减去第l-1棵线段树即可,之就是前缀和的性质。

    用完全二叉树的坐标方法建立一棵线段树需要 O(n)空间,一棵主席树需要n棵线段树,O(n^2)的空间复杂度在数据结构题目里不可能接受(n通常会很大),所以每棵线段树节点编号都要采用记录和紧凑排序的方式,最重要的是节点的按需分配和重(chong)用:完全二叉树建树方法是为了方便编号,把所有空位置也开了节点,在使用紧凑排序方式时,没有必要这么做,只有当我们需要插入一个节点时,才使用一个新的节点,否则就直接指向一个上一棵线段树的对应节点(节点重用),表示那边的子树是空的。这样建树的话,每棵线段树只需要O(logn)的空间,一共n棵就需要O(nlogn)的空间,当时想明白了这一点我高兴了一整天。。。

    以上是静态主席树,O(nlogn)建树,O(logn)完成一次查询,空间开销O(nlogn)。

    数据结构是要用来维护信息的,只能查询信息还是太low。主席树维护的是一维数组,那么修改信息肯定就是修改某个位置的值。把每棵线段树看成一个简单的元素来想,我们现在使用的是前缀和数组的方法维护区间和,前缀和数组每次修改需要O(n),加上线段树就是O(nlogn),这显然不够。维护区间和可以用线段树和树状数组。

    用线段树维护一堆点妆线段树太sxbk了,而且考虑空间开销和时间开销也不可能。底层的点每个点只插入了一个值,每个点只有O(logn)的空间开销,但是倒数第二层每个点需要新建一棵线段树!而且把两个线段树的所有点相加需要对线段树的所有点进行操作,时间复杂度太高(上面静态主席树O(logn)一次的加减操作是指加入一个新的数字或者两棵线段树做减法得到一个点,只需要从根走到叶,不需要修改每个节点)。

    显然很适合用树状数组来维护,加法就是多插一次数字,减法和静态相同,具体实现见代码。

    动态主席树O(nlonlogn)建树,O(lognlogn)完成一次查询或修改,空间开销O(nlognlogn)(一开始以为是O(nlogn),后来发现每次插入一个数都要更新logn棵树,一棵树需要更新logn个点,一共n个数字需要插入)。

    主席树也有缺点,最大的就是空间开销太大,代码量也比较大,我用流氓一样的面向过程写动态主席树也写了100多行。。。另一个缺点是主席树是离线数据结构,不获得所有更新操作无法工作。


POJ_2114_The kth number

题意:

给n个数,每个数字不相同,查询第l个到第r个数字中的第k个数。


之前用的归并树做的,静态主席树的裸题。由于题目中说明了每个数字都不相同,没坑。

用面向对象写的代码,有很多地方写挫了,比如指向空子树,我让指针指向NULL,但这样每次还得特判,如果让指针指向空节点就可以自动递归,节省判断时间,也节省代码量。还有ll和rr信息没必要记录,这题空间要求不大,静态主席树也还好,就没卡。

一开始用new写,结果T到死,于是换了静态空间加cnt指针。

代码:

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;#define mxn 100010int n,m,a[mxn],aa[mxn];struct node{node *ls,*rs;int ll,rr,sum;node(int l=0,int r=0,node* lsn=NULL,node* rsn=NULL){init(l,r,lsn,rsn);}void init(int l=0,int r=0,node* lsn=NULL,node* rsn=NULL){ll=l, rr=r, ls=lsn, rs=rsn;}void merge(){sum=0;if(ls)sum+=ls->sum;if(rs)sum+=rs->sum;}};class president_tree{private:node root[mxn];node nd[mxn<<5];int cnt;void insert(node* pre,node* now,int in){int m=(now->ll+now->rr)>>1;if(now->ll==now->rr){now->sum=1;return;}if(in<=aa[m]){now->rs=pre==NULL?NULL:pre->rs;now->ls=nd+(cnt++);now->ls->init(now->ll,m);insert(pre==NULL?NULL:pre->ls,now->ls,in);}else{now->ls=pre==NULL?NULL:pre->ls;now->rs=nd+(cnt++);now->rs->init(m+1,now->rr);insert(pre==NULL?NULL:pre->rs,now->rs,in);}now->merge();}int find(node* l,node* r,int k){if(r->ll==r->rr)return aa[r->ll];int tl=0,tr;if(l!=NULL)tl=l->ls==NULL?0:l->ls->sum;tr=r->ls==NULL?0:r->ls->sum;int tem=tr-tl;if(k<=tem)return find(l==NULL?NULL:l->ls,r->ls,k);elsereturn find(l==NULL?NULL:l->rs,r->rs,k-tem);}public:void build(){for(int i=0;i<=n;++i){(root+i)->ll=0;(root+i)->rr=n-1;}for(int i=0;i<n;++i)insert(root+i,root+i+1,a[i]);}int query(int l,int r,int k){return find(root+l-1,root+r,k);}}Tree;int main(){while(scanf("%d%d",&n,&m)!=EOF){for(int i=0;i<n;++i){scanf("%d",&a[i]);aa[i]=a[i];}sort(aa,aa+n);Tree.build();for(int i=0;i<m;++i){int l,r,k;scanf("%d%d%d",&l,&r,&k);printf("%d\n",Tree.query(l,r,k));}}return 0;}

BZOJ_1901_Dynamic Rankings

题意:

裸的区间第k大,有修改操作。

题目要求不是很高,有个很诡异的地方就是我用getchar就T,用scanf读字符串就A。。。

代码也是用面向对象写的,不得不再说一遍面向对象写了这么多份代码了,我还是没有对象真是狗。

加了一个bin指针数组记录被完全删除的点,方便节点重用节省空间。

代码也是挺挫。

代码:

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;#define mxn 10010#define mxm 10010int a[mxn],aa[mxn+mxm];int n,m,size;int lowbit[mxn];void set_lowbit(){for(int i=1;i<mxn;++i)lowbit[i]=i&(-i);}struct query{int l,r,k;}q[mxm];struct node{node *ls,*rs;int sum;node(){init();}void init(){ls=rs=NULL;sum=0;}void merge(){sum=0;if(ls)sum+=ls->sum;if(rs)sum+=rs->sum;}};class president_tree{private:node root[mxn];node nd[mxn*400];node* bin[100];int cnt,cb;node* newnode(){if(cb)return bin[--cb];return nd+(cnt++);}void check(node* now){if(cb==100)return;if(now->ls&&!now->ls->sum){bin[cb++]=now->ls;now->ls=NULL;}if(cb==100)return;if(now->rs&&!now->rs->sum){bin[cb++]=now->rs;now->rs=NULL;}}void update(node* now,int x,int l,int r,int num){if(l==r){now->sum+=num;return;}int m=(l+r)>>1;if(x<=aa[m]){if(!now->ls){now->ls=newnode();now->ls->init();}update(now->ls,x,l,m,num);}else{if(!now->rs){now->rs=newnode();now->rs->init();}update(now->rs,x,m+1,r,num);}now->merge();check(now);}public:void build(){cnt=cb=0;for(int i=0;i<=n;++i)(root+i)->init();for(int i=1;i<=n;++i)for(int j=i;j<=n;j+=lowbit[j])update(root+j,a[i-1],0,size-1,1);}void chg(query in){for(int i=in.l;i<=n;i+=lowbit[i]){update(root+i,a[in.l-1],0,size-1,-1);update(root+i,in.r,0,size-1,1);}a[in.l-1]=in.r;}int find(query in){node *ln[50],*rn[50];int lc=0,rc=0;for(int i=in.l-1;i>0;i-=lowbit[i])ln[lc++]=root+i;for(int i=in.r;i>0;i-=lowbit[i])rn[rc++]=root+i;int ll=0,rr=size-1,m;int lcnt,rcnt;while(ll!=rr){m=(ll+rr)>>1;lcnt=rcnt=0;for(int i=0;i<lc;++i)if(ln[i]&&ln[i]->ls)lcnt+=(ln[i])->ls->sum;for(int i=0;i<rc;++i)if(rn[i]&&rn[i]->ls)rcnt+=(rn[i])->ls->sum;if(in.k<=rcnt-lcnt){rr=m;for(int i=0;i<lc;++i)ln[i]=ln[i]?ln[i]->ls:NULL;for(int i=0;i<rc;++i)rn[i]=rn[i]?rn[i]->ls:NULL;}else{in.k-=rcnt-lcnt;ll=m+1;for(int i=0;i<lc;++i)ln[i]=ln[i]?ln[i]->rs:NULL;for(int i=0;i<rc;++i)rn[i]=rn[i]?rn[i]->rs:NULL;}}return aa[ll];}}Tree;void discret(){sort(aa,aa+size);size=unique(aa,aa+size)-aa;}void read(){for(int i=0;i<n;++i){scanf("%d",&a[i]);aa[i]=a[i];}size=n;char tem[5];for(int i=0;i<m;++i){scanf("%s%d%d",tem,&q[i].l,&q[i].r);if(tem[0]=='Q')scanf("%d",&q[i].k);else{q[i].k=0;aa[size++]=q[i].r;}}}int main(){set_lowbit();while(scanf("%d%d",&n,&m)!=EOF){read();discret();Tree.build();for(int i=0;i<m;++i){if(q[i].k)printf("%d\n",Tree.find(q[i]));elseTree.chg(q[i]);}}return 0;}

ZOJ_2112_Dynamic Rankings

主席树例题里面最变态的一道,和BZOJ的题意完全一样。

但是这道题有变态的空间要求,用单棵动态主席树MLE到死,这要是现场赛爸爸早过了,但是秉承着写一棵复杂一点的主席树会让我跟他更熟悉的思想我还是努力A了它。

动态主席树需要O(nlognlogn),直接建树死掉,m比n小,我们建两棵主席树,静态的记录数组信息,动态的记录更新信息,由于很多点都没被更新过,动态树上那些根节点都会空着。然后两颗树一起查询,由于两棵树结构还是完全一样,仍然可以进行加减法。

然后就看出来这道题sxbk了,先是用面向对象卡常数,好吧,那我换面向过程,不用指针。结果还是MLE,卡到死,查了下别人的代码,过了的主席树代码让两颗树共用一个点库,动态树上的根节点也动态开,然后就过了。。。。网上的一份代码在动态树更新的时候做法还浪费了一些点,虽然复杂度没变,但仍然是过了。。。

没有过的面向对象代码:

静态树叫mao,动态树叫jiang,水表已拆

#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<cmath>#include<algorithm>using namespace std;#define mxn 50005#define mxm 10005int n,m,size;int a[mxn],aa[mxn+mxm];int lowbit[mxn+mxm];struct query{int l,r,k;query(){}query(int a,int b,int c){l=a,r=b,k=c;}}q[mxm];struct node{node *ls,*rs;int sum;void init(node* in,int s=0){ls=rs=in;sum=s;}void merge(){sum=ls->sum+rs->sum;}};class static_president_tree{private:node root[mxn];node nd[mxn*15];int cnt;void insert(node* pre,node* now,int in,int l,int r){if(l==r){++now->sum;return;}int m=(l+r)>>1;if(in<=aa[m]){now->ls=nd+(cnt++);now->ls->init(root,pre->ls->sum);now->rs=pre->rs;insert(pre->ls,now->ls,in,l,m);}else{now->rs=nd+(cnt++);now->rs->init(root,pre->rs->sum);now->ls=pre->ls;insert(pre->rs,now->rs,in,m+1,r);}now->merge();}public:void build(){cnt=0;for(int i=0;i<=n;++i)(root+i)->init(root);for(int i=0;i<n;++i)insert(root+i,root+i+1,a[i],0,size-1);}friend int Query(query);}mao;class dynamic_president_tree{private:node root[mxn];node nd[mxm*16*16];int cnt;void update(node* now,int in,int num,int l,int r){if(l==r){now->sum+=num;return;}int m=(l+r)>>1;if(in<=aa[m]){if(now->ls==root){now->ls=nd+(cnt++);now->ls->init(root);}update(now->ls,in,num,l,m);}else{if(now->rs==root){now->rs=nd+(cnt++);now->rs->init(root);}update(now->rs,in,num,m+1,r);}now->merge();}public:void build(){cnt=0;for(int i=0;i<=n;++i)(root+i)->init(root);}void insert(query in){for(int i=in.l;i<=n;i+=lowbit[i]){update(root+i,a[in.l-1],-1,0,size-1);update(root+i,in.r,1,0,size-1);}}friend int Query(query);}jiang;void set_lowbit(){for(int i=1;i<mxn+mxm;++i)lowbit[i]=i&(-i);}void discrete(){sort(aa,aa+size);size=unique(aa,aa+size)-aa;}int Query(query in){node *ll[30],*rr[30];int cntl=0,cntr=0;node *ln=mao.root+in.l-1;node *rn=mao.root+in.r;for(int i=in.l-1;i>0;i-=lowbit[i])ll[cntl++]=jiang.root+i;for(int i=in.r;i>0;i-=lowbit[i])rr[cntr++]=jiang.root+i;int l=0,r=size-1,m;while(l!=r){m=(l+r)>>1;int tem=rn->ls->sum-ln->ls->sum;for(int i=0;i<cntr;++i)tem+=rr[i]->ls->sum;for(int i=0;i<cntl;++i)tem-=ll[i]->ls->sum;if(in.k<=tem){ln=ln->ls;rn=rn->ls;for(int i=0;i<cntr;++i)rr[i]=rr[i]->ls;for(int i=0;i<cntl;++i)ll[i]=ll[i]->ls;r=m;}else{ln=ln->rs;rn=rn->rs;for(int i=0;i<cntr;++i)rr[i]=rr[i]->rs;for(int i=0;i<cntl;++i)ll[i]=ll[i]->rs;l=m+1;in.k-=tem;}}return aa[l];}int main(){cout<<(sizeof(mao)+sizeof(jiang))/1024<<endl;set_lowbit();int cs;scanf("%d",&cs);while(cs--){scanf("%d%d",&n,&m);for(int i=0;i<n;++i){scanf("%d",a+i);aa[i]=a[i];}size=n;char eat[2];for(int i=0;i<m;++i){scanf("%s%d%d",eat,&q[i].l,&q[i].r);if(eat[0]=='Q')scanf("%d",&q[i].k);else{q[i].k=0;aa[size++]=q[i].r;}}discrete();mao.build();jiang.build();for(int i=0;i<m;++i)if(q[i].k)printf("%d\n",Query(q[i]));else{jiang.insert(q[i]);a[q[i].l-1]=q[i].r;}}return 0;}
A了的面向过程代码:

#include<iostream>#include<cstdio>#include<cstring>#include<cmath>#include<algorithm>using namespace std;#define mxn 50010#define mxm 10010int n,m,size;int a[mxn],aa[mxn+mxm];int lowbit(int in){return in&(-in);}void discrete(){sort(aa,aa+size);size=unique(aa,aa+size)-aa;}struct query{int l,r,k;}q[mxm];int root1[mxn],root2[mxn];int ls[mxm*260],rs[mxm*260],sum[mxm*260];int cnt;void init(int id,int s=0){ls[id]=rs[id]=0;sum[id]=s;}void insert(int pre,int now,int in,int val,int l,int r){if(l==r){sum[now]+=val;return;}int m=(l+r)>>1;if(in<=aa[m]){if(!ls[now]){ls[now]=cnt++;init(ls[now],sum[ls[pre]]);}if(!rs[now])rs[now]=rs[pre];insert(ls[pre],ls[now],in,val,l,m);}else{if(!ls[now])ls[now]=ls[pre];if(!rs[now]){rs[now]=cnt++;init(rs[now],sum[rs[pre]]);}insert(rs[pre],rs[now],in,val,m+1,r);}sum[now]=sum[ls[now]]+sum[rs[now]];}void build(){cnt=1;root1[0]=0;init(0);for(int i=0;i<n;++i){root1[i+1]=cnt++;init(cnt-1);insert(root1[i],root1[i+1],a[i],1,0,size-1);}for(int i=0;i<=n;++i)root2[i]=0;}void update(query in){for(int i=in.l;i<=n;i+=lowbit(i)){if(!root2[i]){root2[i]=cnt;init(cnt++);}insert(0,root2[i],a[in.l-1],-1,0,size-1);insert(0,root2[i],in.r,1,0,size-1);}a[in.l-1]=in.r;}int Query(query in){int ln=root1[in.l-1],rn=root1[in.r];int ll[30],rr[30];int cntl=0,cntr=0;int l=0,r=size-1,m;for(int i=in.l-1;i>0;i-=lowbit(i))ll[cntl++]=root2[i];for(int i=in.r;i>0;i-=lowbit(i))rr[cntr++]=root2[i];while(l!=r){m=(l+r)>>1;int tem=sum[ls[rn]]-sum[ls[ln]];for(int i=0;i<cntl;++i)tem-=sum[ls[ll[i]]];for(int i=0;i<cntr;++i)tem+=sum[ls[rr[i]]];if(in.k<=tem){for(int i=0;i<cntl;++i)ll[i]=ls[ll[i]];for(int i=0;i<cntr;++i)rr[i]=ls[rr[i]];ln=ls[ln],rn=ls[rn],r=m;}else{for(int i=0;i<cntl;++i)ll[i]=rs[ll[i]];for(int i=0;i<cntr;++i)rr[i]=rs[rr[i]];ln=rs[ln],rn=rs[rn],l=m+1,in.k-=tem;}}return aa[l];}int main(){int cs;scanf("%d",&cs);while(cs--){scanf("%d%d",&n,&m);for(int i=0;i<n;++i){scanf("%d",a+i);aa[i]=a[i];}size=n;char eat[2];for(int i=0;i<m;++i){scanf("%s%d%d",eat,&q[i].l,&q[i].r);if(eat[0]=='Q')scanf("%d",&q[i].k);else{aa[size++]=q[i].r;q[i].k=0;}}discrete();build();for(int i=0;i<m;++i)if(q[i].k)printf("%d\n",Query(q[i]));elseupdate(q[i]);}return 0;}


0 0
原创粉丝点击