浅谈动态开点线段树

浅谈动态开点线段树

本篇随笔简单讲解一下线段树的常见优化技巧——动态开点。

要学动态开点首先得会线段树,如果不会的话,看官请走这边——

简单线段树详解

权值线段树详解

动态开点的概念和功能

现在要让你维护一棵值域为\(10^9\)的权值线段树。

掐指一算,按线段树开的话,四倍空间是\(4\times10^9\),空间必炸。

于是你开始怀疑这道题有问题,大骂出题人毒瘤

其实你只是不会动态开点。

所谓动态开点,就是用什么开什么。简单的说,就是建立一棵“残疾”的线段树,上面只有询问过的相关节点。从而节约了大量空间,让上面的\(10^9\)权值线段树成为可行。

其实看这个“动态开点”的名字就能隐约猜到这个概念和功能。

这样的话,我们最后的空间复杂度只与询问次数有关,也就是\(O(q\log N)\)

动态开点的代码实现

先放代码,针对代码进行解释。

struct segment_tree
{
    int sum,lson,rson;
}tree[maxq<<2];
int tot;
void update(int &pos,int l,int r,int k)
{
    int mid=(l+r)>>1;
    if(!pos)
        pos=++tot;
   	if(l==r)
    {
        tree[pos].sum+=k;
        return;
    }
    if(x<=mid)
        update(tree[pos].lson,l,mid,k);
    if(y>mid)
        update(tree[pos].rson,mid+1,r,k);
    tree[pos].sum=tree[tree[pos].lson].sum+tree[tree[pos].rson].sum;
}
int main()
{
    int root=0;
    update(root,1,maxn,k);
    return 0;
}

大家会发现,这份代码较之原版的简单线段树并没有多大的差别,其中的精妙就在于节点编号的命名上

原版线段树是一棵完整的二叉树,所以我们采取计算的方式来算出每个节点的左右儿子的编号,具体方式就是(这是条规律):左节点:当前节点×2,右节点:当前节点×2+1。

但是我们动态开点就不一样了。因为是一棵残疾的树,如果我们用这种计算节点编号的方式,就根本没有用了。所以我们这里所有的节点编号,都是人为规定的,玄机就在这个计数变量:tot上。

如果当前去到的节点pos为0,那就说明这个节点没有被使用过,tot+1变成编号给这个节点,与此同时,因为是递归回溯结构,所以这个时候的lson和rson都是已经被确定下来的。(其实tot就是表示当前已经开了多少节点)

这样,我们就总结出了动态开点的玄妙之处:就是节点的编号和节点左右儿子的编号都是乱序的,是我们临到使用之时现加上去的。

嘱咐几句:这个参数变量pos前面的取址符&是不能省略的,因为这个编号是不随递归修改的定值。

简单线段树可以用数组存,但是动态开点的必须用结构体,因为lson和rson是人为规定的。

差不多就这样?

祝同志们AK IOI!

posted @ 2020-04-03 19:50  Seaway-Fu  阅读(3728)  评论(0编辑  收藏  举报