zoukankan      html  css  js  c++  java
  • P3372 线段树模版1

    借着题解系统得梳理一下对于线段树的理解

    先建立一个结构体为下面代码实现打基础

    struct tree{
        int l,r;//存储结点管理的区间范围
        long long pre,add;//pre代表区间权值,add为懒标记,下文会进行讲解
    }t[1000005];

    首先,什么是线段树呢?线段树属于完全二叉树,其中每一个子节点而言,都表示整个序列中的一段子区间。由第一层结点储存单个元素,每个子节点不断向自己的父亲节点传递信息,而父节点存储的信息则是他的每一个子节点信息的整合。

    用数组建立这种数据结构(建树)可以通过如下的递归函数来实现:

    void bulid(int p,int l,int r)
        {
        t[p].l=l;t[p].r=r;//以p为编号的节点维护的区间为l到r
        if(l==r){//l=r的话,这个区间就只有一个数,直接让区间维护的值等于a[i]
            t[p].pre=a[l];
            return;
        }//否则值等于左结点加右结点 
        int mid=l+r>>1;
        bulid(p*2,l,mid);
        bulid(p*2+1,mid+1,r);
        t[p].pre=t[p*2].pre+t[p*2+1].pre;
    } 

    那么,当数据结构已经建成,我们怎样对其中的数据进行修改和查询呢?

    这里要借助一种工具:懒标记。懒标记的作用是记录每次、每个节点要更新的值。由于每个父节点下面都连着子节点,父节点和子节点需要同时实现值的更新。因此,懒标记从最初的父节点开始表示,在父节点值更新后,把子节点的懒标记更新为父节点懒标记,父节点本身懒标记为0。光说可能难以理解,请看代码:

    void spread(int p){
        if(t[p].add){//如果懒标记不为0,修改左右结点的值
            t[p*2].pre+=t[p].add*(t[p*2].r-t[p*2].l+1);
            t[p*2+1].pre+=t[p].add*(t[p*2+1].r-t[p*2+1].l+1);
            t[p*2].add+=t[p].add;//该节点的左右结点打上标记
            t[p*2+1].add+=t[p].add;
            t[p].add=0;//将该节点的懒标记清0
        }
    }

    现在我们就可以愉快地进行数据修改了,从根节点开始搜索,如果某个结点表示的区间完全包含于要修改的值的范围,那么对该结点的值进行上述的懒标记更新。

    void change(int p,int x,int y,int z){
        if(x<=t[p].l && y>=t[p].r)//被覆盖的话,就对其进行修改
        {
            t[p].pre+=(long long)z*(t[p].r-t[p].l+1);
            t[p].add+=z;//打上懒标记
            return;
        }
        spread(p);//如果发现没有被覆盖,那就需要继续向下找,将懒标记下放
        int mid=t[p].l+t[p].r>>1;
        if(x<=mid) change(p*2,x,y,z);//如果要修改的区间覆盖了左结点,就修改左结点 
        if(y>mid) change(p*2+1,x,y,z);//右结点同理
        t[p].pre=t[p*2].pre+t[p*2+1].pre;//最终的值等于左结点的值+右结点的值   
    }

    到此为止,我们只剩下值的查询一个问题,但不用想都能发现,查询和修改根本没有什么区别,通过懒标记同理实现即可。

    long long ask(int p,int x,int y)
    {
        if(x<=t[p].l && y>=t[p].r) return t[p].pre;//如果被覆盖,就返回值
        spread(p);//使用懒标记,查询左右结点
        int mid=t[p].l+t[p].r>>1;
        long long ans=0;
        if(x<=mid) ans+=ask(p*2,x,y);
        if(y>mid) ans+=ask(p*2+1,x,y);//累加答案,返回左右结点值的和
        return ans;
    }

    穿在一起,就构成了线段树的基本操作了

  • 相关阅读:
    刷题94—树(一)
    刷题93—动态规划(十)
    刷题92—动态规划(九)
    刷题91—动态规划(八)
    android Q build 变化
    ubuntu下解压rar文件
    Android PAI (PlayAutoInstall)预装APK 功能
    MTK Android O1平台预置apk
    预置第三方apk到MTK项目相关问题总结
    Android预置Apk方法
  • 原文地址:https://www.cnblogs.com/charlesss/p/10296701.html
Copyright © 2011-2022 走看看