首页 > 编程语言 >线段树 算法笔记

线段树 算法笔记

时间:2023-07-11 09:04:44浏览次数:44  
标签:标记 int 线段 笔记 查询 算法 区间 节点

已知一个长度为 \(n\) 的序列 \(a\),共有 \(m\) 次操作,每次操作如下:

  • 将某区间每一个数加上 \(k\)。
  • 求出某区间每一个数的和。

Luogu - P3372 【模板】线段树 1


之前学过一个算法叫做树状数组,它的本质就是将一个 \([1,x]\) 的区间二进制拆分装化成若干个区间,数组里的每一个元素都代表每一个区间。若需要将 \(x\) 修改只需要将 \(x\) 的父亲节点的元素修改即可,查询就是将区间拆分即可。这样可以在 \(O(\log_2n)\) 的时间复杂度来实现“单点修改,区间查询”或“区间修改,单点查询”的操作。

这一题的类型属于“区间修改,区间查询”,对于这种类型的题,我们很快就会想到一种 \(O(mn\log_2n)\) 的做法对于每一次修改,暴力枚举区间的所有值,将其一一修改,变成“单点修改,区间查询”。或者对于每一次查询暴力查询每一个元素,累加总和,变成“区间修改,单点查询”,但是这些时间复杂度都不够优秀,接下来引入一个更优秀的数据结构叫做——线段树。


线段树顾名思义就是有一堆线段的树,它的根节点的编号为 \(1\) 区间为 \([1,n]\)。线段树里每一个元素的子节点(除叶子节点外)都是将这个区间分成两半。
比如区间 \([l,r]\) 的子节点如下计算:
令 \(mid=\lfloor\frac{l+r}{2}\rfloor\),则区间 \([l,r]\) 的两个子节点分别为 \([l,mid]\) 和 \([mid+1,r]\)。


由此我们可以得出 \(n=6,a=\{1,2,3,4,5,6\}\) 的线段树的样子。

(元素里前两个数表示区间,第三个数表示编号,第四个数表示总和)


从此图我们可以想出构造线段树的方法:

  1. 从根节点开始递归,每次递归到叶子节点(即 \(l=r\) 时)将叶子节点的元素值设为原数组的值。
  2. 回溯时,将遍历到的节点的儿子节点的元素值合并到这个节点即可。

在这里我们定义一个无返回值的函数 \(\text{push_up}(x)\) 表示将节点 \(x\) 的儿子节点的值合并传入节点 \(x\)。

构造线段树代码如下:

void push_up(int u) {
  sum[u] = sum[u << 1] + sum[u << 1 | 1];
}  // 将叶子节点的和传入此节点

void build(int u, int l, int r) {
  if (l == r) {
    sum[u] = a[l];
    return;
  }
  int mid = l + r >> 1;
  build(u << 1, l, mid);
  build(u << 1 | 1, mid + 1, r);
  push_up(u);
}

注意由于线段树的存储方式是用树来存的,所以我们用来存线段树的数组需要开 \(4\) 倍空间。


如果我要查询区间和怎么办呢?
举个栗子,若我们要查询 \([3,5]\) 的区间和我们会从区间 \([1,6]\) 开始遍历。

(图中标有蓝色的标记的点为在遍历过程中会遍历到的元素,标有红色标记的点为在区间 \([3,5]\) 的子区间。)
在每一次遍历中:

  • 若这个区间被区间 \([3,5]\) 完全包含,则回溯此区间的元素值。
  • 若这个区间和区间 \([3,5]\) 的交集不为空,则往下考虑往左右子树遍历。
  • 若都不满足直接回溯。

现在考虑修改,我们很容易就可以想到一种 \(O(n\log_2n)\) 的修改操作,对于修改区间 \([l,r]\) 则让 \(l,l+1,\dots,r-1,r\) 这几个叶子节点区间加上对应值,然后回溯时 \(\text{push_up}\) 即可。

还有一种办法,我们先将每一个线段也就是线段树里的元素加一个属性——“懒标记”,再来看一下上一个图片:

每一个标有红色标记的点我们将他的给他加上当前值的懒标记,注意听然后就是最关键的一步。
我们再查询的过程中遇到有懒标记的点将他的懒标记下传,然后自己的懒标记清零,回溯的时候再 \(\text{push_up}\) 一遍就可以了。

修改加查询的代码如下:

void addtag(int u, int l, int r, LL k) {  // 添加懒标记
  ltag[u] += k, sum[u] += (r - l + 1) * k;
  // 注意这里需要写成 += 否则就会将上一个懒标记清零,会挂。
}

void push_down(int u, int l, int r) {  // 下传标记
  if (ltag[u]) {
    int mid = l + r >> 1;
    addtag(u << 1, l, mid, ltag[u]);
    addtag(u << 1 | 1, mid + 1, r, ltag[u]);
    ltag[u] = 0;
  }
}

void update(int L, int R, int u, int l, int r, LL k) {  // 更新
  if (L <= l && r <= R) {
    addtag(u, l, r, k);
    return;
  }
  push_down(u, l, r);
  int mid = l + r >> 1;
  if (L <= mid) {
    update(L, R, u << 1, l, mid, k);
  }
  if (R > mid) {
    update(L, R, u << 1 | 1, mid + 1, r, k);
  }
  push_up(u);
}

LL query(int L, int R, int u, int l, int r) {  // 查询
  if (L <= l & r <= R) {
    return sum[u];
  }
  push_down(u, l, r);
  int mid = l + r >> 1;
  LL ret = 0;
  if (L <= mid) {
    ret += query(L, R, u << 1, l, mid);
  }
  if (R > mid) {
    ret += query(L, R, u << 1 | 1, mid + 1, r);
  }
  return ret;
}

标签:标记,int,线段,笔记,查询,算法,区间,节点
From: https://www.cnblogs.com/lrx-blogs/p/Segment-Tree-Algorithm-Notes.html

相关文章

  • (转)Docker格式化输出命令:"docker inspect --format" 学习笔记
    原文:https://www.cnblogs.com/kevingrace/p/6424476.htmlDocker--format参数提供了基于Go模板的日志格式化输出辅助功能,并提供了一些内置的增强函数。什么是模板?上图是大家熟悉的 MVC框架(ModelViewController): Model(模型,通常在服务端)用于处理数据、View(视图,客户端代码......
  • Unity3D高级编程主程手记 学习笔记五:网络通讯
    1.C#实现TCP1.1实现所需APIC#提供了TCP的Socket连接API。一般的游戏项目我们不会使用阻塞方式连接和接收。因为我们不会让游戏卡住等待传输链接,大多数情况下我们还是会使用更加平滑的异步操作作为网络连接和收发的操作。常用的API如下:BeginConnect:开始连接Be......
  • Golang学习笔记-常量
    声明常量声明常量关键字:constconst{常量名}{常量类型}或const{常量名}={常量值}预定义常量预定义常量:true,false,iota其中true,false是布尔类型,iota是一个自增常量,从0开始取值它每出现一次,它自身的值会加1iota用法const{ money0=iota//值为0......
  • Golang学习笔记-变量
    声明变量声明变量关键字varvar{变量名称}{变量类型}例子//声明一个变量为v1的整型变量,未赋值时默认值为0varv1int//声明一个变量为v2的浮点型变量,未赋值时默认值为0varv2float32//声明一个变量为v3的数组变量(数组中的元素为整型),未赋值时默认值为nilvarv3......
  • 基础推荐算法概述
    推荐系统可以解决什么问题? 推荐系统的性能可以直接或者间接的影响商品交易系统的成交额,也会影响到用户的购物体验。在实际场景中,用户自己往往也不完全知道自己想要什么,例如,有某银行的积分,想要在其app上兑换一些商品,那么我该兑换什么商品呢?某银行与很多汽车厂商合作,我想通过该......
  • 算法练习-day17
    二叉树110.平衡二叉树题意:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。示例:思路:本题我们可以自下而上判断二叉树是否为平衡二叉树,以上图为示例,我们先判断15是不是平衡二叉树,很明显......
  • 矩阵优化学习笔记
    前言矩阵优化是一种比较靠思维的优化算法,一般简单题考的比较少。个人认为矩阵优化中在运用,所以放了几道题目来讲解。定义矩阵一个\(m\timesn\)的矩阵是一个由\(m\)行\(n\)列元素排列成的矩形阵列。大概长成下面这个样子的。\[A=\underbrace{\begin{bmatrix}a_{1,1......
  • 【数据结构与算法】队列算法题
    TS实现队列interfaceIQueue<T>{//入队enqueue(item:T):void;//出队dequeue():T|undefined;//队首peek():T|undefined;//是否为空isEmpty():boolean;//大小size():number;}classArrayQueue<T>implementsIQueue<T>{......
  • 【数据结构与算法】栈相关算法题(长期更新)
    TS实现栈interfaceIStack<T>{push(e:T):void;pop():T|undefined;peek():T;isEmpyt():boolean;size():number;}//implements:实现接口,一个类可以实现多个接口classArrayStack<T>implementsIStack<T>{privatedata:T[]=[];//pri......
  • 「学习笔记」KMP 算法
    前置知识前缀是指从串首开始到某个位置\(i\)结束的一个特殊子串.真前缀指除了\(S\)本身的\(S\)的前缀.举例来说,字符串abcabeda的所有前缀为{a,ab,abc,abca,abcab,abcabe,abcabed,abcabeda},而它的真前缀为{a,ab,abc,abca,abcab,abcabe,abcabed}.......