线段树进阶—动态开点

  动态开点线段树

  阅读本篇请先学习线段树

  动态开点线段树是一类特殊的线段树,与普通的线段树不同的是,每一个节点的左右儿子不是该点编号的两倍和两倍加一,而是现加出来的。

  一般有两种:为了节约空间,我们会不一次性建好树,而是需要时再建。

  还有一种,就是运用主席树(可持久化线段树)的时候。

  我们先说节约空间用的动态开点线段树。

  我们用lson[u]记录u的左儿子,rson[u]记录u的右儿子(博主不用define于是用ll[u]和rr[u]),l2,r2代表了这次修改所覆盖的范围,其他就不赘述了,以下是建树的代码:

void build(long long u,long long l1,long long r1,long long l2,long long r2)
{
  l[u]=l1;
  r[u]=r1;
  if (l1==r1||l1>=l2&&r1<=r2)//当已经是一个点的时候或被包含的时候就直接返回了
    return;
  if (l2<=(l1+r1)/2)//需建左区间
  {
    ll[u]=++cnt;
    build(ll[u],l1,(l1+r1)/2,l2,r2);
  }
  if (r2>(l1+r1)/2)//需建右区间
  {
    rr[u]=++cnt;
    build(rr[u],(l1+r1)/2+1,r1,l2,r2);
  }
}

  完整代码如下:

void xiafang(long long u)
{
    z[ll[u]]=c[u]*(r[ll[u]]-l[ll[u]]+1);
    c[ll[u]]+=c[u];
    z[rr[u]]=c[u]*(r[rr[u]]-l[rr[u]]+1);
    c[rr[u]]+=c[u];
    c[u]=0;
}
void build(long long u,long long l1,long long r1,long long l2,long long r2)
{
  l[u]=l1;
  r[u]=r1;
  if (l1==r1)
    return;
  if (l2<=(l1+r1)/2)
  {
    ll[u]=++cnt;
    build(ll[u],l1,(l1+r1)/2,l2,r2);
  }
  if (r2>(l1+r1)/2)
  {
    rr[u]=++cnt;
    build(rr[u],(l1+r1)/2+1,r1,l2,r2);
  }
}
void jia(long long u,long long l1,long long r1,long long k)
{
    if (l[u]>r1||r[u]<l1) return;
    if (l[u]>=l1&&r[u]<=r1)
    {
        z[u]+=k*(r[u]-l[u]+1);
        c[u]+=k;
        return;
    }
    if (!ll[u])
    {
        ll[u]=++cnt;
        build(ll[u],l[u],(l[u]+r[u])/2,l1,r1);//需要建树时
    }
    if (!rr[u])
    {
        rr[u]=++cnt;
        build(rr[u],(l[u]+r[u])/2+1,r[u],l1,r1);
    }
    if (c[u]) xiafang(u);
    jia(ll[u],l1,r1,k);
    jia(rr[u],l1,r1,k);
    z[u]=z[ll[u]]+z[rr[u]];
}
long long qui(long long u,long long l1,long long r1)
{
    if(!u) return 0;//就是没有这个点,也就是未赋值,返回0
    if (l[u]>r1||r[u]<l1) return 0;
    if (l[u]>=l1&&r[u]<=r1) return z[u];
    if (c[u]) xiafang(u);
    return (qui(ll[u],l1,r1)+qui(rr[u],l1,r1));
}  

  还有一种是可持久化线段树中的运用

  这个可以直接看博主写的可持久化线段树的博文,博主将这段复制到那边去了一份,就不用来回看了……

  其实很简单,只要将上文中的新建节点变成在重新赋值时以模式树进行修改就行了,代码如下:

void jia(long long mo,long long u,long long x,long long k)//mo:模式版本的代表该区间的节点,u:我们要构造的节点,x:区间位置,k:要修改成的值
{
    l[u]=l[mo];
    r[u]=r[mo];
    if (l[u]==r[u])
    {
        z[u]=k;
        return;
    }//这个和build很像
    if (x<=(l[u]+r[u])/2)//x在左儿子
    {
        rr[u]=rr[mo];//右边和模式版本一样
        cnt++;
        ll[u]=cnt;
        jia(ll[mo],ll[u],x,k);
        z[u]=z[ll[u]]+z[rr[u]];
    }
    else
    {
        ll[u]=ll[mo];//左边就和模式版本一样
        cnt++;
        rr[u]=cnt;
        jia(rr[mo],rr[u],x,k);
        z[u]=z[ll[u]]+z[rr[u]];//x在右儿子    }
}

  其实用一个计数器记录一下开到了哪个节点,然后需要再开点时增加,就算是动态开点的重要之处,注意不满足lson[u]=u*2,rson[u]=u*2+1,因此需要数组记录。

原文地址:https://www.cnblogs.com/szbszb/p/11309369.html