用户工具

站点工具


2020-2021:teams:no_morning_training:shaco:知识点:数据结构:线段树

这是本文档旧的修订版!


线段树

前言

解决的问题

在线维护修改、查询区间最值、求和,可以扩充到二位线段树、三位线段树。

复杂度

一维线段树每次更新和查询的时间复杂度为$O(log{N})$

概念

二叉树的结构,每一个节点代表一个区间,每个子节点代表父结点区间的一半。 初始版本左儿子下标是父亲下标的两倍,右儿子下标为父亲下标的两倍+1。

操作

建树

自上而下,同时更新父节点。

code

code

const int maxn = 100005;
int a[maxn],t[maxn<<2];        //a为原来区间,t为线段树
 
void Pushup(int k){        //更新函数,这里是实现最大值 ,同理可以变成,最小值,区间和等
    t[k] = max(t[k<<1],t[k<<1|1]);
}
 
//递归方式建树 build(1,1,n);
void build(int k,int l,int r){    //k为当前需要建立的结点,l为当前需要建立区间的左端点,r则为右端点
    if(l == r)    //左端点等于右端点,即为叶子节点,直接赋值即可
        t[k] = a[l];
    else{
        int m = l + ((r-l)>>1);    //m则为中间点,左儿子的结点区间为[l,m],右儿子的结点区间为[m+1,r]
        build(k<<1,l,m);    //递归构造左儿子结点
        build(k<<1|1,m+1,r);    //递归构造右儿子结点
        Pushup(k);    //更新父节点
    }
}

点更新

自上而下

code

code

//递归方式更新 updata(p,v,1,n,1);
void updata(int p,int v,int l,int r,int k){    //p为下标,v为要加上的值,l,r为结点区间,k为结点下标
    if(l == r)    //左端点等于右端点,即为叶子结点,直接加上v即可
        a[k] += v,t[k] += v;    //原数组和线段树数组都得到更新
    else{
        int m = l + ((r-l)>>1);    //m则为中间点,左儿子的结点区间为[l,m],右儿子的结点区间为[m+1,r]
        if(p <= m)    //如果需要更新的结点在左子树区间
            updata(p,v,l,m,k<<1);
        else    //如果需要更新的结点在右子树区间
            updata(p,v,m+1,r,k<<1|1);
        Pushup(k);    //更新父节点的值
    }
}

区间查询

自上而下,只统计所查询区间的子区间,在某一个递归进程中查询到则该进程结束。

code

code

//递归方式区间查询 query(L,R,1,n,1);
int query(int L,int R,int l,int r,int k){    //[L,R]即为要查询的区间,l,r为结点区间,k为结点下标
    if(L <= l && r <= R)    //如果当前结点的区间真包含于要查询的区间内,则返回结点信息且不需要往下递归
        return t[k];
    else{
        Pushdown(k);    /**每次都需要更新子树的Lazy标记*/
        int res = -INF;    //返回值变量,根据具体线段树查询的什么而自定义
        int mid = l + ((r-l)>>1);    //m则为中间点,左儿子的结点区间为[l,m],右儿子的结点区间为[m+1,r]
        if(L <= m)    //如果左子树和需要查询的区间交集非空
            res = max(res, query(L,R,l,m,k<<1));
        if(R > m)    //如果右子树和需要查询的区间交集非空,注意这里不是else if,因为查询区间可能同时和左右区间都有交集
            res = max(res, query(L,R,m+1,r,k<<1|1));
 
        return res;    //返回当前结点得到的信息
    }
}

区间更新

使用lazy_tag,更新时只下放到最高一层的更新区间的子区间。查询时再更细致地下放。

code

code

void Pushdown(int k){    //更新子树的lazy值,这里是RMQ的函数,要实现区间和等则需要修改函数内容
    if(lazy[k]){    //如果有lazy标记
        lazy[k<<1] += lazy[k];    //更新左子树的lazy值
        lazy[k<<1|1] += lazy[k];    //更新右子树的lazy值
        t[k<<1] += lazy[k];        //左子树的最值加上lazy值
        t[k<<1|1] += lazy[k];    //右子树的最值加上lazy值
        lazy[k] = 0;    //lazy值归0
    }
}
 
//递归更新区间 updata(L,R,v,1,n,1);
void updata(int L,int R,int v,int l,int r,int k){    //[L,R]即为要更新的区间,l,r为结点区间,k为结点下标
    if(L <= l && r <= R){    //如果当前结点的区间真包含于要更新的区间内
        lazy[k] += v;    //懒惰标记
        t[k] += v;    //最大值加上v之后,此区间的最大值也肯定是加v
    }
    else{
        Pushdown(k);    //重难点,查询lazy标记,更新子树
        int m = l + ((r-l)>>1);
        if(L <= m)    //如果左子树和需要更新的区间交集非空
            update(L,R,v,l,m,k<<1);
        if(m < R)    //如果右子树和需要更新的区间交集非空
            update(L,R,v,m+1,r,k<<1|1);
        Pushup(k);    //更新父节点
    }
}
2020-2021/teams/no_morning_training/shaco/知识点/数据结构/线段树.1590574292.txt.gz · 最后更改: 2020/05/27 18:11 由 shaco