zoukankan      html  css  js  c++  java
  • 浅谈动态开点线段树

    浅谈动态开点线段树

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

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

    简单线段树详解

    权值线段树详解

    动态开点的概念和功能

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

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

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

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

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

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

    这样的话,我们最后的空间复杂度只与询问次数有关,也就是(O(qlog 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!

  • 相关阅读:
    前言(CSDN也有Markdown了,好开森)
    One usage of recurison: the tower of Hanoi
    使用Android注解来改善代码
    mysql生产环境____主从同步修复案例
    不同类型的指针
    C++ 对象模型
    为什么需要模版成员方法
    理解 traits
    C++ 异常处理
    传const引用代替传值
  • 原文地址:https://www.cnblogs.com/fusiwei/p/12628596.html
Copyright © 2011-2022 走看看