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;
}

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

猜你喜欢

转载自www.cnblogs.com/charlesss/p/10296701.html