动态开点线段树——节约资源,你值得拥有

简介:

发现,有的时候,线段树需要维护的区间很大很大,但是实际用到的节点很少很少。

那么,我们干脆就不要开这么多的节点,用到的时候再向内存要。

也就是说,我们建立了一棵残疾的线段树,缺少很多枝叶,但是绝对够用了。

画个图大概理解一下

实心边框的点都是我们申请内存给的,虚的点是没用的。就算申请也不用,实在是浪费资源。

所以,

我们开局只有一个根,

装备全靠给。

枝叶全靠给。

 

例如我们要建立一个权值线段树,但是在线操作不让你离散化,值域又是inf级别的,

像这样,即使这个区间的范围很大,但是如果询问q比较少的话,我们只需要qloginf个节点,就可以办到。

 

具体代码实现:

不同的操作,但是大同小异。

还是类似于一般线段树的。

 

框架:

function(int &x,int l,int r,int blablabla){
    if(!x){
        //建新节点,并处理信息
        if(l==r) //叶子节点由于是真正要用的节点(对于单点),往往还要特殊记录信息 
    }
    if(blablabla)  return ???//判断是否能返回等等
    (int ret)  // 如果需要返回时停留更新信息,就弄一个ret 
    if(blablabla) return (t[x].ls,l,mid,blablabla)
    if(blablabla) return (t[x].rs,mid+1,r,blablabla);
    (pushup(x)) //回溯后更新 
}

 

 

发现和主席树有点像,但是省空间的思想还是有些不同的。

主席树是:多棵线段树,利用相邻之间有很大部分是相同的。可以在之前线段树基础上建立线段树。

    特点:许多线段树共用儿子节点

动态开点线段树:一棵线段树,利用实际用到的点不多,少开了很多节点。

    特点:区间范围很大(通常不能直接开下)

共同点:(都是线段树)

    都通过新加入的节点有限,进行的空间优化。使得时间空间复杂度都是logn/次

 

例题:(里面也有本篇的部分讲解)

 

NOIP2017 列队

 

动态开点线段树合并

upda:2018.9.22

主席树相邻的有很大的关系,那么动态开点线段树呢?

动态开点线段树也可以支持合并。

函数:

int merge(int x,int y,int l,int r){
    if(!x||!y) return x|y;
    if(l==r){
        do something on the leaf
    }
    else{
    t[x].ls=merge(t[x].ls,t[y].ls,l,mid);
    t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r);
    pushup(x);
    }
    return x;
}

理解:空节点直接返回,然后本质其实是相当于利用y的儿子们,修改x儿子们的信息。

同样也是会达到共用儿子的目的。

注意叶子节点的特判暴力合并。

其实类似左偏树的合并。

但是复杂度的原理证明不太相同。

这个merge的证明主要是通过,每成功合并一次,节点数会少1个。

如果开始有mlogn个节点,那么最多合并mlogn次,复杂度就可以保证。

 

空间复杂度:总共nlogn,也就是开始节点个数。

发现,其实我们把动态开点线段树和主席树联系起来了!

但是,主席树不能直接合并多棵(其实也可以,但就和动态开点线段树没区别了。),必须在一棵基础上建立,而且不能灵活支持修改。

动态开点线段树就可以支持一次次地合并多棵为一棵,但是同样,合并后,再修改就比较麻烦了。

 

例题:

[Vani有约会]雨天的尾巴——树上差分+动态开点线段树合并

 

 

upda:2018.11.1

根据上面两个题的做法,

动态开点线段树,还经常开很多棵,

对于某个点要打很多不同的标记,并且父子之间标记要快速合并的话,

那么每个点动态开点线段树就比较优秀了。

(相较于一般的差分只是差分一种标记,这个就比较强大了。)

甚至:

天天爱跑步——树上差分

 

这个题用个动态开点线段树直接打差分标记也是可以的。。。(如果你不会全局桶的骚操作的话)

 

posted @ 2018-09-04 09:19  *Miracle*  阅读(6602)  评论(0编辑  收藏  举报