线段树的详细讲解

1、线段树

       线段树也被称为区间树,英文名为Segment Tree或者Interval tree,是一种高级的数据结构。它主要用于维护区间信息(要求满足结合律)。与树状数组相比,它可实现效率为O(log⁡n) 区间修改,还可以同时支持多种操作(加、乘),更具通用性。

2、线段树常用操作

(1)add操作,即实现数组arr([l,r])的某区间内([L, R]) 所有的数+C

        add(L, R, C, l, r, rt)

(2)updata操作,即实现数组arr([l,r])的某区间内([L, R]) 所有的数更新为V

updata(L, R, V, l, r, rt)

(3)querry操作,即实现数组arr([l,r])的某区间内([L, R])的

        querry(L,R,l, r, rt)

3、线段树的建立

(1)为实现通过数组建立一个满二叉树的形式,选择4N(N,size(arr))的大小构建,多出的位置补零。若数组的下标从1开始,节点为 i 的左孩子为 2*i ,右孩子为 2*i+1,则可利用位运算进行优化,i<<1 和 i<<1 | 1

(2)懒惰标记,标记的含义:本区间已经被更新过了,但是子区间不会被更新(所谓的懒住了),被更新的信息是什么(区间求和只用记录有没有被访问过,而区间加减乘除等多种操作的问题则要记录进行的是哪一种操作)。

(3)相对标记和绝对标记
        1)相对标记指的是可以共存的标记,且打标记的顺序与答案无关,即标记可以叠加。 比如说给一段区间中的所有数字都 + a ,我们就可以把标记叠加一下,比如上一次打了一个 + 1 的标记,这一次要给这一段区间 + 5 ,那么就把 + 1  的标记变成 + 6 。例如接下来add操作中的lazy标记。
        2)绝对标记是指不可以共存的标记每一次都要先把标记下传再给当前节点打上新的标记。这些标记不能改变次序,否则会出错。 比如说给一段区间的数字重新赋值,或是给一段区间进行多种操作。例如update操作中的change标记updata标记 

4、add操作的具体实现流程(通过举例演示)

线段树:arr = [0,0,0,0,0,0,0,0],     sum = [0,0,0,0,0,0,0,0]

Add(L, R, C, l, r, rt] l = 1, r= 8; rt = 1;

(1)任务范围为:[L,R] = [1~4] , C= 2        

(2)任务范围为:[L,R] = [5~8] , C= 2     

(3)任务范围为:[L,R] = [1~8] , C= 2   

 (4)任务范围为:[L,R] = [1~4] , C= 2   

分发策略:将父范围的懒更新(lazy[rt]),分发给子范围;左右子范围更新lazy和sum;最后将父范围的lazy置零。

 5、updata操作的具体实现流程

updata(L, R, V, l, r, rt] l = 1, r= 8; rt = 1;

(1)在add(3)的基础上进行更新操作,任务范围:[L, R] = [1-8]    V = 2;

(2)任务范围:[L, R] = [1-4]    V = 2;

 6、代码实现

class SegmentTree{
    //arr[]为原序列的信息从0开始,但在arr里是从1开始的
    //sum[]模拟线段树维护区间和
    //lazy[]为累加懒标记
    //change[]为更新的值
    //update[]为更新懒标记
public:
    int MAXN;
    int* arr;
    int* sum;
    int* lazy;
    int* change;
    int* update;

public:
    SegmentTree(vector<int> origin){
        MAXN = origin.size() + 1;   
        arr = new int[MAXN];
        for(int i = 1; i < MAXN; i++){
            arr[i] = origin[i-1];
        }

        sum = new int[MAXN << 2];
        lazy = new int[MAXN << 2];
        change = new int[MAXN << 2];
        update = new int[MAXN << 2];
    }

    void build(int l, int r, int rt){
        //在arr[l~r]范围上,去build, 1~N
        //rt:这个范围在sum中的下标
        if(l == r){
            sum[rt] = arr[l];
            return;
        }
        int mid = (l + r) >> 1;
        //建造左子树
        build(l, mid, rt<<1);
        //建造右子树
        build(mid+1, r, rt<<1 | 1);
        //上传合并
        pushUp(rt);
    }

    void pushUp(int rt){
        sum[rt] = sum[rt<<1] + sum[rt<<1 | 1];
    }


    void add_func(int L, int R, int C, int l, int r, int rt){
        //[L~R]任务范围
        //[l~r]表达的范围
        //rt 去哪找l, r范围上的信息

        //任务的范围彻底覆盖了当前表达的范围
        if(L <= l && R >= r){
            sum[rt] += C * (r - l + 1);
            lazy[rt] += C;
            return;
        }

        //任务并没有把l..r全包住
        //要把当前任务下发,检查是否下发
        int mid = (l+r) >> 1;
        //下发之前的lazy add任务
        pushdown(rt, mid-l+1, r-mid);

        //判断左孩子是否需要接到任务
        if(L <= mid){
            add_func(L, R, C, l, mid, rt<<1);
        }
        //判断右孩子是否需要接到任务
        if(R > mid){
            add_func(L, R, C, mid+1, r, rt<<1 | 1);
        }

        //左右孩子做完任务后,更新sum信息
        pushUp(rt);
    }


    void update_func(int L, int R, int V, int l, int r, int rt){
        if(L <= l && R >= r){
            update[rt] = true;
            change[rt] = V;
            sum[rt] = (r - l + 1)*V;
            lazy[rt] = 0;
            return;
        }

        int mid = (r + l) >> 1;
        pushdown(rt, mid-l+1, r-mid);

        if(L <= mid){
            update_func(L, R, V, l, mid, rt<< 1);
        }

        if(R > mid){
            update_func(L, R, V, mid+1, r, rt<<1 | 1);
        }

        pushUp(rt);
    }


    long querry(int L, int R, int l, int r, int rt){
        if(L <= l && R >= r){
            return sum[rt];
        }

        int mid = (r + l) >> 1;
        pushdown(rt, mid-l+1, r-mid);
        long ans = 0;
        if(L <= mid){
            ans += querry(L, R, l, mid, rt<<1);
        }

        if(R > mid){
            ans += querry(L, R, mid+1, r, rt<<1 | 1);
        }

        return ans;
    }


    void pushdown(int rt, int ln, int rn){
        if(update[rt]){
            update[rt << 1] = true;
            update[rt<<1 | 1] = true;

            change[rt << 1] = change[rt];
            change[rt<<1 | 1] = change[rt];

            sum[rt<<1] = change[rt]*ln;
            sum[rt<<1 | 1] = change[rt]*rn;

            lazy[rt<<1] = 0;
            lazy[rt<<1 | 1] = 0;

            update[rt] = false;
        }
        
        if(lazy[rt] != 0){

            lazy[rt << 1] += lazy[rt];
            sum[rt << 1] += lazy[rt] * ln;

            lazy[rt << 1 | 1] += lazy[rt];
            sum[rt<<1 | 1] += lazy[rt]*rn;

            lazy[rt] = 0;
        }
    }


    void PrintArr(int* arr){
        for(int i = 0; i < MAXN; i++){
            cout << arr[i] << ",";
        }
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值