数据结构--线段树

news2024/11/29 8:47:06
写在前面:
学习之前需要知道以下内容:
1. 递归
2. 二叉树

文章目录

  • 线段树
    • 介绍
    • 用途
    • 建树
    • 修改
      • 单点修改
      • 区间修改
    • 查询
  • 代码实现。
    • 建树
    • 更新
    • lazy传递
    • 查询
  • 练习
    • 洛谷 P3372 【模板】线段树 1
      • 题目描述
      • 题解

线段树

介绍

线段树是一种二叉树,也可以归类为二叉搜索树。可以将区间的修改、维护和查询的时间复杂度优化为log级别的。

构成
如一段1-6区间
在这里插入图片描述
可以分开为1-3和4-6然后继续放下拆分直到拆分为一个数的时候。
也就可以用树的标识。
在这里插入图片描述

用途

区间的加法

也可以结合 扫描线 用于二维面积并问题。

建树

存储方式我们使用数组来进行建树。这个要用到的就是二叉树的知识了。(对于 i i i 下标节点,左儿子下标为 2 ∗ i 2*i 2i,右儿子下标为 2 ∗ i + 1 2*i +1 2i+1 父节点下标为 i / / 2 i//2 i//2

修改

单点修改

通过二叉树查询下标在进行修改。(二叉树查询为log级别)
然后更新大区间的值。

在这里插入图片描述
如修改下标2为数字3那么则需要更新为
在这里插入图片描述

区间修改

如将1-5的的值+2
则下面部分都需要+2
在这里插入图片描述
但是这样好像也没有啥优势,和数组一个个+2是一样的甚至,还多一些。

我们可以在这加一个缓存技术。
1-5就只需要更新这2个就可以了,然后在其lazy数组上设置为+2.
更新的值为长度*加的值
这里 10 10 10 就是 + 2 ∗ 3 +2*3 +23 3 3 3 加上 2 ∗ 2 2*2 22
在这里插入图片描述
每次当要用到子节点的时候就需要将lazy标记下存了。
如现在 [ 1 − 3 ] [1-3] [13]的标记为 2 2 2 当我们要用到 [ 1 ] [1] [1]的时候
[ 3 ] [3] [3]就+2
[ 1 − 2 ] [1-2] [12]也+2
[ 1 − 3 ] [1-3] [13] 设置为0
然后继续递归

查询

这里和二叉树查询一样
不过需要注意的是。lazy标记下存的操作,在这里也是需要的。

如查询 [ 1 − 2 ] [1-2] [12]的值。
首先在 [ 1 − 6 ] [1-6] [16]的左,然后发现在 [ 1 − 3 ] [1-3] [13]的左,但是lazy为+2
所以 l z a y [ 1 − 3 ] = 0 , l z a y [ 1 − 2 ] = 2 , l a z y [ 3 ] = 2 lzay[1-3] = 0,lzay[1-2]=2,lazy[3]=2 lzay[13]=0,lzay[12]=2,lazy[3]=2.
所以 [ 1 − 2 ] = l z a y [ 1 − 2 ] + [ 1 − 2 ] = 2 + 7 = 9 [1-2] = lzay[1-2]+[1-2] = 2+7=9 [12]=lzay[12]+[12]=2+7=9

代码实现。

建树

使用递归来实现

    /**
     * 建树
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @return 当前位置的值
     */
    public long build(int l, int r, int pos) {
        if (l == r) {
            return tree[pos] = mapping[l - 1];
        } else {
            int mid = (l + r) >> 1;
            return tree[pos] = build(l, mid, pos << 1)
                    + build(mid + 1, r, pos << 1 | 1);
        }
    }

更新

每次从根节点开始进行。
如果查询和边界没有交点的时候停止递归
在这里插入图片描述
如果完全包括,那么就将这个区间更新。
更新规则:

  1. 区间值+value*长度
  2. 区间lazy +value

在这里插入图片描述
其他情况就是一部分在一部分不在,那么就进行向子节点查询
注意如果该节点lazy值不为空则需要向下传递。
向下查找完后,需要将子节点添加的值向本节点更新。
在这里插入图片描述

    /**
     * 更新
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @param value 更新值
     * @return 当前位置的值
     */
    public long getUpdate(int l, int r, int pos, int value) {
        if (r < updateL || l > updateR)
            return 0;
        if (l >= updateL && r <= updateR) {
            lazy[pos] += value;
            long v = (long) value * (r - l + 1);
            tree[pos] += v;
            return v;
        } else {
            int mid = (l + r) >> 1;
            updateLazy(l, r, pos, mid);
            long ul = getUpdate(l, mid, pos << 1, value);
            long ur = getUpdate(mid + 1, r, pos << 1 | 1, value);
            tree[pos] += ul + ur;
            return ul + ur;
        }
    }

lazy传递

更新规则如下,

  1. 子节点的lazy设置为本节点的lazy
  2. 子节点数值更新
  3. 本节点lazy设置为0
    /**
     * 更新懒标记
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @param mid 中间位置
     */
    private void updateLazy(int l, int r, int pos, int mid) {
        if (lazy[pos] != 0) {
            lazy[pos << 1] += lazy[pos];
            lazy[pos << 1 | 1] += lazy[pos];
            tree[pos << 1] += lazy[pos] * (mid - l + 1);
            tree[pos << 1 | 1] += lazy[pos] * (r - mid);
            lazy[pos] = 0;
        }
    }

查询

这个没有太多好说的,和更新挺像的
一样需要注意的是lazy值的下移。

    /**
     * 查询
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @return 当前位置的值
     */
    public long getN(int l, int r, int pos) {
        if (l > getR || r < getL)
            return 0;
        if (l >= getL && r <= getR) {
            return tree[pos];
        } else {
            int mid = (l + r) >> 1;
            int i = pos << 1;
            int j = i | 1;
            updateLazy(l,r,pos,mid);
            return getN(l, mid, i) +
                    getN(mid + 1, r, j);
        }
    }

练习

洛谷 P3372 【模板】线段树 1

题目描述

如题,已知一个数列,你需要进行下面两种操作:

  1. 将某区间每一个数加上 k k k
  2. 求出某区间每一个数的和。

输入格式

第一行包含两个整数 n , m n, m n,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 m m m 行每行包含 3 3 3 4 4 4 个整数,表示一个操作,具体如下:

  1. 1 x y k:将区间 [ x , y ] [x, y] [x,y] 内每个数加上 k k k
  2. 2 x y:输出区间 [ x , y ] [x, y] [x,y] 内每个数的和。

输出格式

输出包含若干行整数,即为所有操作 2 的结果。

样例 #1

样例输入 #1

5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4

样例输出 #1

11
8
20

提示

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 m ≤ 10 m \le 10 m10
对于 70 % 70\% 70% 的数据: n ≤ 10 3 n \le {10}^3 n103 m ≤ 10 4 m \le {10}^4 m104
对于 100 % 100\% 100% 的数据: 1 ≤ n , m ≤ 10 5 1 \le n, m \le {10}^5 1n,m105

保证任意时刻数列中所有元素的绝对值之和 ≤ 10 18 \le {10}^{18} 1018

【样例解释】

题解

import java.io.*;

public class Main {
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static final PrintWriter print = new PrintWriter(System.out);
    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static void main(String[] args) throws IOException {
        int n = nextInt();
        int m = nextInt();
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = nextInt();
        }
        SegmentTrees s = new SegmentTrees(a);
        for (int i = 0; i < m; i++) {
            int j = nextInt();
            if (j == 1) {
                s.update(nextInt(), nextInt(), nextInt());
            } else
                print.println(s.get(nextInt(), nextInt()));
        }
        print.flush();
    }
}

class SegmentTrees {
    private final long[] tree;//线段树
    private final int size;
    private final int[] mapping;
    private final long[] lazy;

    public SegmentTrees(int[] mapping) {
        this.size = mapping.length;
        this.mapping = mapping;
        this.tree = new long[size * 3 + 1];
        this.lazy = new long[size * 3 + 1];
        build(1, size, 1);
    }

    /**
     * 建树
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @return 当前位置的值
     */
    public long build(int l, int r, int pos) {
        if (l == r) {
            return tree[pos] = mapping[l - 1];
        } else {
            int mid = (l + r) >> 1;
            return tree[pos] = build(l, mid, pos << 1)
                    + build(mid + 1, r, pos << 1 | 1);
        }
    }


    public void update(int l, int r, int value) {
        this.updateL = l;
        this.updateR = r;
        getUpdate(1, size, 1, value);
    }

    private int updateL;
    private int updateR;

    /**
     * 更新
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @param value 更新值
     * @return 当前位置的值
     */
    public long getUpdate(int l, int r, int pos, int value) {
        if (r < updateL || l > updateR)
            return 0;
        if (l >= updateL && r <= updateR) {
            lazy[pos] += value;
            long v = (long) value * (r - l + 1);
            tree[pos] += v;
            return v;
        } else {
            int mid = (l + r) >> 1;
            updateLazy(l, r, pos, mid);
            long ul = getUpdate(l, mid, pos << 1, value);
            long ur = getUpdate(mid + 1, r, pos << 1 | 1, value);
            tree[pos] += ul + ur;
            return ul + ur;
        }
    }

    /**
     * 更新懒标记
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @param mid 中间位置
     */
    private void updateLazy(int l, int r, int pos, int mid) {
        if (lazy[pos] != 0) {
            lazy[pos << 1] += lazy[pos];
            lazy[pos << 1 | 1] += lazy[pos];
            tree[pos << 1] += lazy[pos] * (mid - l + 1);
            tree[pos << 1 | 1] += lazy[pos] * (r - mid);
            lazy[pos] = 0;
        }
    }

    public long get(int l, int r) {
        this.getL = l;
        this.getR = r;
        return getN(1, size, 1);
    }

    private int getL;
    private int getR;

    /**
     * 查询
     * @param l 左边界
     * @param r 右边界
     * @param pos 当前位置
     * @return 当前位置的值
     */
    public long getN(int l, int r, int pos) {
        if (l > getR || r < getL)
            return 0;
        if (l >= getL && r <= getR) {
            return tree[pos];
        } else {
            int mid = (l + r) >> 1;
            int i = pos << 1;
            int j = i | 1;
            updateLazy(l,r,pos,mid);
            return getN(l, mid, i) +
                    getN(mid + 1, r, j);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/523962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【5G RRC】5G中的服务小区和邻区测量方法

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

STL配接器(容器适配器)—— stack 的介绍使用以及模拟实现。

注意 &#xff1a; 以下所有文档都来源此网站 &#xff1a; http://cplusplus.com/ 一、stack 的介绍和使用 stack 文档的介绍&#xff1a;https://cplusplus.com/reference/stack/stack/ 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&…

Matlab进阶绘图第20期—带类别标签的三维柱状图

带类别标签的三维柱状图是一种特殊的三维柱状图。 与三维柱状图相比&#xff0c;带类别标签的三维柱状图通过颜色表示每根柱子的所属类别&#xff0c;从而可以更加直观地表示四维/四变量数据。 由于Matlab中未收录带类别标签的三维柱状图的绘制函数&#xff0c;因此需要大家自…

Java 使用 jdbc 连接 mysql

简介 Java JDBC 是 Java Database Connectivity 的缩写&#xff0c;它是一种用于连接和操作数据库的标准 API。Java JDBC 可以让 Java 程序通过 JDBC 驱动程序连接到各种不同类型的数据库&#xff0c;并且执行 SQL 语句来实现数据的读取、插入、更新、删除等操作。在本篇文章中…

Springboot整合Flowable流程引擎

文章目录 前言1. Flowable的主要表结构1.1 通用数据表&#xff08;通用表&#xff09;1.2运行时数据表&#xff08;runtime表&#xff09;1.3.历史数据表&#xff08;history表&#xff09;1.4. 身份数据表&#xff08;identity表&#xff09;1.5. 流程定义数据表&#xff08;r…

C++: 并行加速图像读取和处理的过程

文章目录 1. 目的2. 设计3. 串行实现4. 并行实现5. 比对&#xff1a;耗时和正确性6. 加速比探讨 1. 目的 读取单张图像&#xff0c;计算整图均值&#xff0c;这很好实现&#xff0c;运行耗时很短。 读取4000张相同大小的图像&#xff0c;分别计算均值&#xff0c;这也很好实现…

【OpenCv • c++】形态学技术操作 —— 开运算与闭运算

&#x1f680; 个人简介&#xff1a;CSDN「博客新星」TOP 10 &#xff0c; C/C 领域新星创作者&#x1f49f; 作 者&#xff1a;锡兰_CC ❣️&#x1f4dd; 专 栏&#xff1a;【OpenCV • c】计算机视觉&#x1f308; 若有帮助&#xff0c;还请关注➕点赞➕收藏&#xff…

openGauss5.0.0在vscode成功调试

之前在虚拟机上编译成功过&#xff0c;但今天启动数据库的时候出现权限错误问题&#xff0c;我重新删除了data文件夹&#xff0c;重新初始化启动数据库还是不成功&#xff0c;后来对报错文件进行赋权&#xff0c;成功解决&#xff01; 问题&#xff08;一&#xff09; 1.启动…

图像水印MATLAB实验

文章目录 一、实验目的二、实验内容1. 简单的可见水印嵌入实验2. 不可见脆弱水印实验3. 不可见鲁棒水印实验 一、实验目的 了解数字图像水印技术的基本原理、分类和应用。掌握简单的可见水印和不可见水印的嵌入方法。实现一种基于DCT的不可见鲁棒水印&#xff0c;并进行水印鲁…

Dubbo 服务端源码深入分析 (7)

目录 1. 前提 2. 认识 Protocol 和 ProxyFactory Protocal ProxyFactory Dubbo服务流程 服务端源码分析 测试代码&#xff1a; Protocal代理的源码 ProxyFactory源码&#xff1a; 获取invoker对象 具体步骤 1. 我们调用的是ProxyFactory的代理对象的getInvoker方法…

Linux线程同步(6)——更高并行性的读写锁

互斥锁或自旋锁要么是加锁状态、要么是不加锁状态&#xff0c;而且一次只有一个线程可以对其加锁。读写锁有 3 种状态&#xff1a;读模式下的加锁状态&#xff08;以下简称读加锁状态&#xff09;、写模式下的加锁状态&#xff08;以下简称写加锁状态&#xff09;和不加锁状态&…

django视图(request请求response返回值)

一、视图函数介绍 视图就是应用中views.py中定义的函数&#xff0c;称为视图函数 def index(request):return HttpResponse("hello world&#xff01;") 1、视图的第一个参数必须为HttpRequest对象&#xff0c;还可能包含下参数如通过正则表达式组获取的位置参数、通…

VBA——01篇(入门篇——简单基础语法)

VBA——01篇&#xff08;入门篇——简单基础语法&#xff09; 1. 语法格式1.1 简单语法1.2 简单例子 2. 变量2.1 常用数据类型2.2 声明变量的常用方式2.3 简单例子 3. 单元格赋值3.1 直接赋值3.2 拷贝单元格 4. 简单的逻辑语法4.1 简单if4.2 简单for循环4.2.1 简单语法例子4.2.…

基于混合整数二阶锥(MISOCP)的配电网重构(附matlab代码)

参考资料&#xff1a;主动配电网网络分析与运行调控 (sciencereading.cn) 配电网重构是指在满足配电网运行基本约束的前提下&#xff0c;通过改变配电网中一个或多个开关的状态对配电网中一个或多个指标进行优化。通过配电网重构&#xff0c;可以在不增加设备投资的情况下&…

注解实现:判空赋值

工作中的小玩意~~ 流程&#xff1a; 注解实现反射工具类 注解定义及实现 注解定义&#xff1a; Documented Target(ElementType.FIELD) Retention(RetentionPolicy.RUNTIME) public interface CheckParam {String value() default "-1"; }简单解释上述其相关注解…

哈工大2023春计算机组成原理真题回忆

仅供同学参考&#xff0c;严禁用作商业用途 如发现将追究责任 2023-5-14 属鼠经历了计算机组成原理考试 现将本人真题回忆如下&#xff1a;欢迎大家补充&#xff0c;并期待大家一起参与这个开源的项目。 致谢:真诚感谢草履虫同学提供的图片 15个选择部分回忆如下 &#xff1a…

【历史上的今天】4 月 13 日:Damn Small Linux 首次发布;谷歌关闭短网址服务;数学先驱出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 13 日&#xff0c;在 2006 年的今天&#xff0c;盛大文学榕树下网站被民营企业收购&#xff1b;原创文学网站榕树下被民营传媒集团欢乐传媒收购&#xff…

hnust 湖南科技大学 2023 软件测试技术 期中考试 复习资料

前言 写的比较匆忙&#xff0c;重点也不明确&#xff0c;没什么参考价值致谢&#xff1a;ly&#xff0c;zxq重点来源&#xff1a;信安※&#xff1a;补充内容★&#xff1a;重点✦&#xff1a;个人推测考点考试范围&#xff1a;1-9章获取最新版本 题型 判断&#xff1a;10简…

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1)

AMBER分子动力学模拟之TOP准备-- HIV蛋白酶-抑制剂复合物(1) 我们以HIV蛋白酶-抑制剂复合物为例子&#xff0c;跑Amber动力学模拟 下载1phv 从PBD下载文件&#xff1a;https://www.rcsb.org/ PDB文件预处理 我们以 “protein(water) ligandcomplex” 为例来说一下如何处…

系统设计基本原理-耦合与内聚

耦合 耦合是模块之间的相互独立性(互相连接的紧密程度)的度量&#xff0c;耦合取决于各个模块之间接口的复杂程度、调用模块的方式以及通过接口的信息类型等。 耦合类型 无直接耦合&#xff1a;指两个模块之间没有直接的关系&#xff0c;它们分别从属于不同模块的控制与调用&…