首页 > 其他分享 >线段树学习

线段树学习

时间:2023-02-22 19:33:32浏览次数:29  
标签:rt 线段 mid 累加 学习 区间 sum 500

  • 参考资料: 《算法竞赛入门到进阶》、左神算法课
  • 线段树示意图, 图片摘自《算法竞赛入门到进阶》
  • 解决区间更新、区间查询、区间累加问题
  • 基于原数组的4倍数组长度建立sum数组,完成对原数组origin的树化
  • 线段树采用一种懒惰的做法,当累加(更新)操作是针对一个整个的区间时,只对这个线段区间做整体上的修改,其内部的元素先不变,当这段区间的一致性被破坏时才将变化值传递给子区间,时间复杂度 O(nlogn)
  • 字段解释
    • max:原始数组的最大长度
    • arr[]:原始数组src的copy, arr[]下标从1开始, 用数组表示一棵树时, 根节点下标为 i, 则左孩子为 (i << 1),右孩子为 (i << 1 | 1), 所以树的根节点用1表示比较方便
    • sum[]:累加和数组, 存储arr[]上一段区间的范围, 其下标用 rt表示
    • lazy[]:懒数组, 存储sum[]区间上未下发的累加信息
    • change[]:sum[]范围上所有的值被更新成了啥
    • update[]:表示change[]中的特殊值是是要更新的值还是系统默认值, 比如数字 0, 是sum[rt]区间被更新为 0还是系统默认值为 0呢? true表示被更新的值
  • 建树操作
    • 如果区间分为 l == r,说明此时递归到叶子节点了, 直接给sum[rt]赋值为arr[l]即可
    • 否则递归构建左右子树,当程序返回到后序位置时说明rt节点的左右子树都构建完毕, 自然地sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]
  • 累加操作:
    • 考虑数组范围[1, 500]上进行如下两次累加操作 [1, 500]范围上的所有数加上 2, [6, 366]范围上的所有数加上 6
      • 第一次累加2时判断区间 [1, 500] == [1, 500]所以'懒'住信息不下发, 标记lazy[rt] += 2
      • 第二次累加6时判断区间 [6, 366] <= [1, 500], 所以将区间[1, 500]上次懒住的信息下发一层, 然后再进行本次累加操作, 此后分别递归[6, 250]、[251, 500]....等等范围, 直到信息能够被懒住, 期间每次遇到上一次的懒任务都先下发。
    • 为什么只下发一层呢?
      • 因为本次累加操作当前看的层的线段区间一致性被破坏, 第一次[1, 500]累加了2但是第二次[6, 366]上又累加了6,破坏了线段区间的一致性所以将第一次累加的2下发一层,
      • 因为是递归操作,所以处理下一层时会发现[1, 250]区间存在懒信息累加2, 本次又要在[3, 250]上累加6,又破坏了[1, 250]区间上的一致性,所以继续下发上次的lazy[rt]=2的累加信息,然后在处理本次的lazy[rt]=6。
    • 什么叫线段区间一致性?
      • 对于区间[1, 5]上只能有一段覆盖区间[1, 5]的累加信息, 不能既有[1, 5]上累加2, [2, 5]上累加6这种操作, 所以要清算历史旧账
  • 更新操作:
    • 类似累加操作, 只不过操作的数组是 update[]和 change[], 同样现下发懒人物任务后在进行更新, 防止更新操作之后的累加任务丢失, 比如[1, 500]上的数更新为3, 然后[1, 500]上的数累加2, 则先更新是对的, 先累加在更新会导致累加结果丢失, 即[1, 500]上的数都变成了3, 实际是5才对
  • 区间查询操作:
    • 类似累加操作, 如果懒住了直接返回, 否则现下发之前的懒任务, 然后递归计算答案后返回
package com.lxpstu.tree;

/**
 * @Author: lxpStu
 * @Date: 2023/02/16/17:52
 * @Description: 线段树模板
 *
 */
public class SegmentTree {
    int max;
    int[] arr;
    int[] sum;
    int[] lazy;
    int[] change;
    boolean[] update;

    public SegmentTree(int[] origin) {
        this.max = origin.length + 1;
        this.arr = new int[max];
        System.arraycopy(origin, 0, arr, 1, max - 1);
        this.sum = new int[max << 2];
        this.lazy = new int[max << 2];
        this.change = new int[max << 2];
        this.update = new boolean[max << 2];
    }

    /**
     * rt:arr[l...r]范围在sum[]中的下标
     */
    public void build(int l, int r, int rt){
        if(l == r) {
            // 此时递归到叶子节点了
            sum[rt] = arr[l];
            return;
        }
        // 取中点, 构建左右子树
        int mid = l + (r - l) >> 1;
        build(l, mid, rt << 1);
        build(mid + 1, r, rt << 1 | 1);
        // 递归处理完左右孩子的sum[rt]之后处理自己的sum[rt]
        pushUp(rt);
    }

    /**
     * sum[rt]表示线段树的区间范围 l...r上的数字之和
     * C:  [L, R]本次下发的任务范围, 将[L, R]范围上的数加上C
     */
    public void add(int rt, int l, int r, int L, int R, int C){
        if(L <= l && r <= R){
            // 任务范围包括了线段树区间
            // 区间上所有数(r - l + 1个)都加上 C
            sum[rt] += C * (r - l + 1);
            // 懒标记
            lazy[rt] += C;
            return;
        }
        int mid = l + (r - l >> 1);
        // 下发之前的老任务
        pushDown(rt, mid - l + 1, r - mid);
        // 处理如区间[1, 500]下发任务[3, 384]这种无法懒住的情况, 递归下发任务给左右孩子
        if (L <= mid) {
            add(rt << 1, l, mid, L, R, C);
        }
        if (R > mid) {
            add(rt << 1 | 1, mid + 1, r, L, R, C);
        }
        // 处理自己的sum[rt]
        pushUp(rt);
    }

    public void update(int L, int R, int C, int l, int r, int rt){
        if(L <= l && r <= R){
            update[rt] = true;
            change[rt] = C;
            sum[rt] = C * (r - l + 1);
            lazy[rt] = 0;
            return;
        }
        int mid = l + (r - l >> 1);
        // 下发之前的懒任务
        pushDown(rt,mid - l + 1, r - mid);
        if(L <= mid){
            update(L, R, C, l, mid, rt << 1);
        }
        if(R > mid){
            update(L, R, C, mid + 1, r, rt << 1 | 1);
        }
        pushUp(rt);
    }

    public long query(int L, int R, int l, int r, int rt){
        if (L <= l && r <= R) {
            return sum[rt];
        }
        int mid = l + (r - l >> 1);
        pushDown(rt, mid - l + 1, r - mid);
        long res = 0;
        if (L <= mid) {
            res += query(L, R, l, mid, rt << 1);
        }
        if(R > mid){
            res += query(L, R, mid + 1, r, rt << 1 | 1);
        }
        return res;
    }

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

    /**
     * ln:左孩子节点个数, rn:右孩子节点个数
     */
    private void pushDown(int rt, int ln, int rn){
        // 必须先检查update再检查add,因为最近一次更新之后如果存在累加操作之后再次更新的话会将累加的值清空
        // 先累加再更新会导致白累加,最终还是被更新了。
        // 如果是[1, 500]上的数更新为3, 然后[1, 500]上的数累加2, 则先更新是对的, 先累加在更新会导致累加
        // 结果丢失, 导致[1, 500]上的数都变成了3, 实际是5才对
        if(update[rt]){
            update[rt << 1] = true;
            update[rt << 1 | 1] = true;
            change[rt << 1] = change[rt];
            change[rt << 1 | 1] = change[rt];
            lazy[rt << 1] = 0;
            lazy[rt << 1 | 1] = 0;
            sum[rt << 1] = change[rt] * ln;
            sum[rt << 1 | 1] = change[rt] * rn;
            update[rt] = false;
        }
        // 当前节点懒不下去了, 下发一层, 然后将自己的懒信息置为 0
        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;
        }
    }
}

标签:rt,线段,mid,累加,学习,区间,sum,500
From: https://www.cnblogs.com/lxpStu/p/17145394.html

相关文章

  • tensorflow.js 多分类,机器学习区分企鹅种类
    前言:    在规则编码中,我们常常会遇到需要通过多种区间判断某种物品分类。比如二手物品的定价,尽管不是新品没有SKU但是基本的参数是少不了。想通过成色来区分某......
  • 可解释性学习LIME在图像分类中的应用
    参考文献#加载需要的包#https://blog.csdn.net/weixin_42347070/article/details/106455763#https://lime-ml.readthedocs.io/en/latest/lime.html?highlight=explanation......
  • prometheus监控平台学习
    架构图PrometheusServer:收集指标和存储时间序列数据,并提供查询接口ClientLibrary:客户端库PushGateway:短期存储指标数据。主要用于临时性的任务Exporters:采......
  • #yyds干货盘点 react笔记之学习之使用create-react-app创建文件
    前言我是歌谣我有个兄弟巅峰的时候排名c站总榜19叫前端小歌谣曾经我花了三年的时间创作了他现在我要用五年的时间超越他今天又是接近兄弟的一天人生难免坎坷大不了从......
  • 2023.2.22学习记录
    今天学习了Android开发的文本控件的初步,以及像素的知识文本的字体大小DP,与sp的差别。xml,java,<string>等的了解。1,<resources><stringname="app_name">MyApplicati......
  • E029Web学习笔记-Maven基础
    一、Maven概述1、简介Maven是一个非常强大的项目管理和综合工具; 2、Maven依赖管理Maven将Java项目所需要的Jar包放在一个统一的仓库里面,多个项目可以共同使用; 3、项目的一......
  • E028Web学习笔记-Redis
    一、概述1、简介Redis是完全开源免费的,遵守BSD协议,是一个高性能的NOSQL系列的非关系型key-value数据库;数据存储在内存中的数据库; 2、关系型数据库与非关系型数据库关系型......
  • 学习使用Markdown
    学习使用Markdown标题二级标题可以写到六级标题字体Hello,World!Hello,World!Hello,World!Hello,World!Hello,World!引用这里是引用的地方分割线图片![......
  • 从Bug中学习--Bug根因分析法
    来源:http://www.51testing.com/html/31/n-4456831.html一提起测试,大多数人很容易就会联想到Bug。的确,测试的日常工作离不开Bug,测试工作很重要的一部分就是发现Bug。但......
  • 2023.2.22软件工程学习日报
      所花时间:代码量:博客量:3了解到的知识点:今天首先在安装了AndroidStudio的基础上了解了以下几点内容1、Android的项目架构(某个文件夹具体是干什么的)2、自带的SQLi......