zoukankan      html  css  js  c++  java
  • 学习笔记 --- 线段树

    开始切割数据结构,种树之路,学习了一下线段树,写一下心得与体会
    

    一线段树摆上:
    线段树...
    线段树的原理:将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。
    线段树中有如下定理:
    定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
    定理:n>=3时,一个[1,n]的线段树可以将[1,n]的任意子区间[L,R]分解为不超过个子区间。
    一般存储采用数组直接存储:
    当前点的位置为now,那么它的左子树位置为now*2,右子树位置为now*2+1(和二叉树存储同理)
    那么是线段树的模板(此处以一个维护和的线段树演绎)

    建树过程:

    void updata(int now)
    {
        sum[now]=sum[now<<1]+sum[now<<1|1];
    }//求和过程 
    //此过程线段树核心过程,一般不同的线段树,最大的不同处就在此处
    void build(int left,int right,int now)
    {
        if (left==right)
            {
                sum[now]=tree[left];
                return;
            }//每当达到叶节点就把值更新一下
        int mid=(left+right)>>1;
        build(left,mid,now<<1);//左子树建树
        build(mid+1,right,now<<1|1);//右子树建树
        updata(now); //容易遗忘但是特别重要的一个语句,在更新完子数后根据子数修改根
    }//建树过程 

    点修改过程:
    更新一个点的值

    void point_change(int now,int left,int right,int data,int locate)
    {
        if (left==right)
            {
                sum[now]+=data;
                return;
            }//如果到达这个点的位置,更新
        int mid=(left+right)>>1;
        if (locate<=mid)//如果要修改的点位于左子树,向左查询
            point_change(now<<1,left,mid,data,locate);
        else//反之向右
            point_change(now<<1|1,mid+1,right,data,locate);
        updata(now);//仍旧不能忘,更新完点就要更新根
    } //点修改过程 

    区间修改过程:
    更新区间【left,right】的值

    标记的含义:
    本节点的统计信息已经根据标记更新过了,但是本节点的子节点仍需要进行更新。即,如果要给一个区间的所有值都加上1,那么,实际上并没有给这个区间的所有值都加上1,而是打个标记,记下来,这个节点所包含的区间需要加1.打上标记后,要根据标记更新本节点的统计信息,比如,如果本节点维护的是区间和,而本节点包含5个数,那么,打上+1的标记之后,要给本节点维护的和+5。这是向下延迟修改,但是向上显示的信息是修改以后的信息,所以查询的时候可以得到正确的结果。有的标记之间会相互影响,所以比较简单的做法是,每递归到一个区间,首先下推标记(若本节点有标记,就下推标记),然后再打上新的标记,这样仍然每个区间操作的复杂度是O(log2(n))。

    标记有相对标记和绝对标记之分:
    相对标记是将区间的所有数+a之类的操作,标记之间可以共存,跟打标记的顺序无关(跟顺序无关才是重点)。
    所以,可以在区间修改的时候不下推标记,留到查询的时候再下推。绝对标记是将区间的所有数变成a之类的操作,打标记的顺序直接影响结果,所以这种标记在区间修改的时候必须下推旧标记,不然会出错。

    注意,有多个标记的时候,标记下推的顺序也很重要,错误的下推顺序可能会导致错误。

    void section_change(int LEFT,int RIGHT,int data,int left,int right,int now)//LEFT,RIGHT表示需要更新的区间,data为更新值,left,right为当前点的控制区间,now当前位置
    {
        if (LEFT<=left && RIGHT>=right)//如果本区间完全在更新的区间范围,就更新
            {
                sum[now]+=data*(right-left+1);
                delta[now]+=data;//惰性标记,子区间的值需要依此修改
                return;
            }
        int mid=(left+right)>>1;
        if (LEFT<=mid)//判断左子树和修改区间有无交集,有交集则递归
            section_change(LEFT,RIGHT,data,left,mid,now<<1);
        if (RIGHT>mid)//原理同上
            section_change(LEFT,RIGHT,data,mid+1,right,now<<1|1);
        updata(now);//同理需要更新根
    }//区间修改过程

    区间查询过程:
    查询区间【left,right】的值

    void pushdown(int now,int leftn,int rightn)//把根的标记下放到子数,确保答案的正确性
    {
        if (delta[now]!=0)
            {
                delta[now<<1]+=delta[now];
                delta[now<<1|1]+=delta[now];//标记下推
                sum[now<<1]+=delta[now]*leftn;
                sum[now<<1|1]+=delta[now]*rightn;//修改子节点的值
                delta[now]=0;//清除当前点的标记
            } 
    }//标记下放函数
    
    int query(int LEFT,int RIGHT,int left,int right,int now)
    {
        if (LEFT<=left && RIGHT>=right)
            return sum[now];//在区间内,直接返回即可
        int mid=(left+right)>>1;
        pushdown(now,mid-left+1,right-mid);//标记下推(不加这步,结果出错)
        int total=0;//累计答案
        if (LEFT<=mid) total+=query(LEFT,RIGHT,left,mid,now<<1);
        if (RIGHT>mid) total+=query(LEFT,RIGHT,mid+1,right,now<<1|1);
        return total;       
    } //区间查询    

    主过程的调用:

    int main()
    {
    //建树   
    Build(1,n,1);   
    //点修改  
    Update(L,C,1,n,1);  
    //区间修改   
    Update(L,R,C,1,n,1);  
    //区间查询   
    int ANS=Query(L,R,1,n,1); 
    }

    说了这么多,还是需要大量题目的练习( ⊙ o ⊙ )啊!…..

  • 相关阅读:
    Python学习笔记模式匹配与正则表达式之使用和不使用正则表达式
    关于Jqury的一些杂碎
    客户端验证模型
    导航(摘)
    解决了DIV+CSS一个问题
    购买了新书《Bootstrap用户手册—设计响应式网站》及简介Bootstrap是什么
    博客园的博客页面开通了,今天!
    利用Cmake 将最新版本OBS编译成windows版本。
    C语言无法使用引用,一定要使用怎么办? ------指针的指针做参数
    linux core文件的打开和分析
  • 原文地址:https://www.cnblogs.com/DaD3zZ-Beyonder/p/5346261.html
Copyright © 2011-2022 走看看