线段树&&树状数组 总结

来源:互联网 发布:php prematch 编辑:程序博客网 时间:2024/05/24 15:40

前言
在对这三个数据结构进行了粗浅的学习之后,博主发现数据结构的世界是多么的美妙。
然后在博主的专业作死技能加持之下,三个数据结构的大战一触即发……


一、单点修改及区间查询

树状数组

对于某些题目(如,单点更新并且查询区间和),那么这个时候,我们会发现用树状数组也十分吃香,毕竟这正是树状数组所擅长的!它的时间复杂度均为O(log(n)),相比于线段树,它的空间复杂度尤其小得多。

void update(int target[],int pos,int val){    for(;pos<=n;pos+=lowbit(pos))      target[pos]+=val;}int query(int target[],int pos){    int sum=0;    for(;pos;pos-=lowbit(pow))      sum+=target[pos];    return sum;}

zkw线段树

然后接着,zkw线段树(%%%%)对此也是再擅长不过了,看起来十分的欢快。
毕竟是非递归的,常数小,在大部分情况下,可以大大加快操作速度。
思想
单点修改:先通过N直接找到叶子节点,然后再向上更新父亲节点。
区间查询:为了避免错误,我们在找叶子节点之前,先转闭区间为开区间获取指针。左边的指针如果是父亲的左子树就累加其右子树的答案,右边的指针如果是父亲的右子树就累加左子树的答案。直到两指针的父亲节点相同。
为了更好的理解,建议手动模拟~

void update(int pos,int val){    for(pos+=N;pos;pos>>=1)      sum[pos]+=val;}void query(int l,int r){    int ans=0;    for(l+=N-1,r+=N+1;l^r^1;l>>=1,r>>=1)    {        if(~l&1)          ans+=sum[l^1];        if(r&1)          ans+=sum[r^1];    }}

普通线段树

但是同时,我们也知道,线段树更新、查询等操作的复杂度也为O(log(n))。但是线段树的常数较大。尤其对于这些比较入门的题目的时候,代码量会比较大,也就使得我们更容易出错。尽管如此,我们还是贴一下它操作的代码,以示敬意。

inline void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];}inline void pushdown(int ln,int rn,int rt){    lazy[rt<<1]+=lazy[rt];    lazy[rt<<1|1]+=lazy[rt];    sum[rt<<1]+=ln*lazy[rt];    sum[rt<<1|1]+=rn*lazy[rt];    lazy[rt]=0;}void update(int l,int r,int L,int c,int rt){    if(l==r)    {           sum[rt]+=c;          return;      }      int m=(l+r)>>1;      if(L<=m)      update(l,m,L,c,rt<<1);      else      update(m+1,r,L,c,rt<<1|1);      pushup(rt);   }   int query(int l,int r,int L,int R,int rt){    if(L<=l&&r<=R)      return sum[rt];    int m=(l+r)>>1,ans=0;    if(lazy[rt])      pushdown(m-l+1,r-m,rt);    if(L<=m)      ans+=query(l,m,L,R,rt<<1);    if(m<R)      ans+=query(m+1,r,L,R,rt<<1|1);    return ans;}

由此可见,在应对一些简单问题的时候,线段树不一定是最好的选择。

二、区间更新及区间查询

树状数组

由于其在第一部分,时间及空间复杂度的优异表现,于是各路神犇就对其作出了更强的改进。
dalao says:乱写能AC,暴力踩标程
思想
以下的update(),query()函数的含义同上。
假设原数组为a,利用差分的思想处理出s数组,即s[i]=a[i]a[i1]
那么显然,我们可以得到这样的式子:a[i]=ij=1s[j]
则我们会发现

i=1na[i]=i=1nj=1is[j]

我们再展开一下,就会变成这样,一个三角形!
一张丑陋的截图
补全为矩形,然后减去补上的三角形,继续化简:
i=1na[i]=ni=1ns[i]i=1n(i1)s[i]

将后面的的ni=1(i1)s[i]作为si进行维护,即si[i]=(i1)s[i]
然后就可以做到区间更新与修改了!

scanf("%d",&type);  if(type==1)//[l,r]+v  {      scanf("%d%d%d",&l,&r,&v);    update(s,l,v);    update(s,r+1,-v);    update(si,l,v*(a-1));    update(si,r+1,-v*b);}  else//[l,r]->sum{      scanf("%d%d",&l,&r);      sum1=(l-1)*query(s,l-1)-sigma(si,l-1);      sum2=r*query(s,r)-query(si,r);      printf("%lld\n",sum2-sum1);  }  

zkw线段树

代码中一些变量的含义:
ln:s一路走来已经包含了几个数
rn:t一路走来已经包含了几个数
x:本层中包含的数
思想
lazy数组依然是懒惰标记,不过利用了差分的思想,化绝对为相对。因此,在更新与查询的时候,[L,R]所对应的区间上升到父亲相同之后,还要继续向上一直上升到根节点。

void update(int L,int R,int c){      int s,t,ln=0,rn=0,x=1;      for(s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1,x<<=1)    {          sum[s]+=r*ln;          sum[t]+=c*rn;          if(~s&1) lazy[s^1]+=c,sum[s^1]+=c*x,ln+=x;          if( t&1) lazy[t^1]+=c,sum[t^1]+=c*x,rn+=x;      }      for(;s;s>>=1,t>>=1){          sum[s]+=c*ln;          sum[t]+=c*rn;      }   }int query(int L,int R){      int s,t,ln=0,rn=0,x=1;      int ans=0;      for(s=N+L-1,t=N+R+1;s^t^1;s>>=1,t>>=1,x<<=1){          if(lazy[s]) ans+=lazy[s]*ln;          if(lazy[t]) ans+=lazy[t]*rn;          if(~s&1) ans+=sum[s^1],ln+=x;          if( t&1) ans+=sum[t^1],rn+=x;       }      for(;s;s>>=1,t>>=1){          ans+=lazy[s]*ln;          ans+=lazy[t]*rn;      }      return ans;  }  

普通线段树

相对来说,普通线段树应对的比较从容,代码量也没有增加太多,但依旧是最大的……

inline void pushup(int rt){sum[rt]=sum[rt<<1]+sum[rt<<1|1];}inline void pushdown(int ln,int rn,int rt){    lazy[rt<<1]+=lazy[rt];    lazy[rt<<1|1]+=lazy[rt];    sum[rt<<1]+=ln*lazy[rt];    sum[rt<<1|1]+=rn*lazy[rt];    lazy[rt]=0;}void update(int l,int r,int L,int R,int c,int rt){    if(L<=l&&r<=R)    {        lazy[rt]+=c;        sum[rt]+=(l-r+1)*c;        return ;    }    int m=(l+r)>>1;    if(L<=m)      update(l,m,L,R,c,rt<<1);    if(m<R)      update(m+1,r,L,R,c,rt<<1|1);    pushup(rt);}int query(int l,int r,int L,int R,int rt){    if(L<=l&&r<=R)      return sum[rt];    int m=(l+r)>>1,ans=0;    if(lazy[rt])      pushdown(m-l+1,r-m,rt);    if(L<=m)      ans+=query(l,m,L,R,rt<<1);    if(m<R)      ans+=query(m+1,r,L,R,rt<<1|1);    return ans;}

由此可见,普通线段树相对于树状数组与zkw线段树来说,更加灵活多变,甚至能够应对更加复杂的情况而游刃有余。

三、总结

树状数组:如果能用的话,在时间、空间复杂度上估计都可以暴踩线段树(用了都说好)
zkw线段树:常数小,性质多,跑的也的确比普通线段树要快得多,但似乎应用上还有一些局限性,不一定适用于所有的题目。感觉很强的样子,只不过要搞懂它的精髓可能还需要时间……
普通线段树:很灵活,能够应对复杂多变的题目,到底是经典。

原创粉丝点击