在对数组进行操作的时候,我们有时会需要获取数组某个区间的信息,如该区间内的最值、区间和等。我们可以使用枚举的方式去获取这些信息,但是这样做的平均时间复杂度期望为O(n),数据范围一大,这样的方式就基本稳稳的超时了。

于是,线段树应运而生。线段树是一种高级数据结构,基于二分思想,以O(logn)的时间进行区间查找与修改

线段树常见的操作有:

1、建立一棵线段树(build);
2、查询某区间的信息(ask);
3、修改某个元素的值(replace);
4、给某个区间内的元素全部加上一个值(add)。

(ps:代码中的p<<1、p<<1|1为位运算优化,效果等同于p*2,p*2+1。)

首先,我们来看看如何建立线段树。从上图来看,容易看出,线段树是一颗二叉树,每个节点都代表了一个区间,左孩子和右孩子都将父亲分为了两段。所以,我们可以采用二倍孩子法来储存。这里我采用了区间和线段树作为讲解模板。

template<typename T>//泛型,线段树内的元素可以指定类型
struct Tree
{int l,r;//该节点所管区间的左右边界T val;//这里保存的是将要查询的信息,如区间和、区间最值等。
}tree[SIZE<<2];

前面说了,线段树是基于二分思想的,所以,我们采用递归+分治的方式进行建树。

具体方式:

1、将区间边界赋给该节点;
2、如果左右边界相等,即区间内只有一个元素,则将数组内的值赋值给该叶节点,直接返回;
3、递归左孩子;
4、递归右孩子;
5、用左右孩子的信息来更新当前节点的信息。

代码:

void Build(int p,int l,int r,T *a)//p指的是当前的区间节点
{tree[p].l=l,tree[p].r=r;//1if(l==r){tree[p].val=a[l];return;}//2int mid=(l+r)>>1;Build(p<<1,l,mid,a);//3Build(p<<1|1,mid+1,r,a);//4tree[p].val=tree[p<<1].val+tree[p<<1|1].val;//5
}

然后,就是查询区间信息了。因为我们建树的时候已经把各个节点的信息更新好了,所以现在可以直接查找了。

具体步骤:

1、如果当前节点管辖区间正好等于查找区间,直接返回该节点的信息值;
2、如果当前节点的左孩子正好完全包含查找区间,则递归到左孩子查找;
3、如果当前节点的右孩子正好完全包含查找区间,则递归到右孩子查找;
4、如果查找区间在左孩子中有一部分,在右孩子中有一部分,则左右区间都递归查找,再根据需要的信息进行操作。

代码:

T Ask(int p,int l,int r)
{if(tree[p].l==tree[p].r){return tree[p].val;}int mid=(tree[p].l+tree[p].r)>>1;if(l<=mid&&r<=mid) return Ask(p<<1,l,r);else if(r>mid&&l>mid) return Ask(p<<1|1,l,r);else return Ask(p<<1,l,r)+Ask(p<<1|1,l,r);
}

然后,则是单点元素更改了。在理解完上面的内容后,单点更改就非常容易了。

具体步骤:

1、如果当前区间节点只有一个元素,且此元素为待修改元素,直接修改,返回;
2、如果节点在左区间,则递归左区间;
3、反之,递归右区间;
4、用左右节点信息更新当前节点信息。

代码:

void Replace(int p,int index,T val)
{if(tree[p].l==tree[p].r){tree[p].val=val;return;}//1int mid=(tree[p].l+tree[p].r)>>1;if(index>mid) Replace(p<<1|1,index,val);//2else Replace(p<<1,index,val);//3tree[p].val=tree[p<<1].val+tree[p<<1|1].val;//4
}

接下来,则是重头戏——区间整体增加(add)。

按照以上的方式,进行区间整体增加的方式只能是对区间内每个元素进行单点更改,共计length次修改,时间复杂度为O(nlogn)。对于某些题目,显然是不能满足速度需求的。那么,有更快的算法吗?

观察其他的线段树教学blog可以发现,在区间修改时,使用了一个叫lazytag,也就是懒标记的东西。为什么叫做懒标记?因为它很懒

我们给每个节点额外增加一个成员变量tag,也就是上述的“懒标记”。

struct Tree
{int l,r;T val,tag;//懒标记
}tree[SIZE<<2];

懒标记为什么叫做懒标记?因为它的作用是作为一个传递标记使用。在更改区间时,找到一个包含在待更改区间内的区间节点后,不继续向下更新,而是更新完自身后,只把增加值给懒标记。等到以后要访问下面的节点时,才将当前节点懒标记下传给下面的子节点。

这么一来,在区间修改操作中,我们的均摊复杂度就只有O(logn)了(常数比较大)。

值得注意的一点是,由于在调整区间值时,我们并没有把底下的区间更改,只是挂在了上面的lazytag上,所以在单点更改和获取信息时,记得要先将lazytag下放,再向下递归。

首先是下放的函数:
void Spread(int p) { if(tree[p].tag)//如果tag为0就没必要下放了 { int tag=tree[p].tag; //一个区间管n个元素,所以val要加上tag*n tree[p<<1].val+=tag*(tree[p<<1].r-tree[p<<1].l+1);//更新左子树 tree[p<<1|1].val+=tag*(tree[p<<1|1].r-tree[p<<1|1].l+1);//更新右子树 tree[p<<1].tag+=tag,tree[p<<1|1].tag+=tag;//下放 tree[p].tag=0;//tag下传完毕后就可以释放了 } }
然后是区间修改:

void Add(int p,int l,int r,T val)
{if(l<=tree[p].l&&r>=tree[p].r)//如果包含了待修改区间,直接修改tag和val,不再向下下传与递归。{tree[p].val+=val*(tree[p].r-tree[p].l+1);tree[p].tag+=val;return;}Spread(p);//下传懒标记int mid=(tree[p].l+tree[p].r)>>1;if(l<=mid) Add(p<<1,l,mid,val);//左递归if(r>mid) Add(p<<1|1,mid+1,r,val);//右递归tree[p]val=tree[p<<1].val+tree[p<<1|1].val;//更新节点信息
}

ask和replace只要在左右向下递归前下传tag就可以了。

转载于:https://www.cnblogs.com/MaxDYF/p/9780808.html

线段树(SegmentTree)学习笔记相关推荐

  1. 珂朵莉树/ODT 学习笔记

    珂朵莉树/ODT 学习笔记 起源自 CF896C.珂朵莉yyds! 核心思想 把值相同的区间合并成一个结点保存在 set 里面. 用处 骗分.只要是有区间赋值操作的数据结构题都可以用来骗分.在数据随机 ...

  2. 看动画学算法之:线段树-segmentTree

    文章目录 简介 最小线段树 线段树的构建 线段树的搜索 线段树的更新 线段树的复杂度 简介 什么是线段树呢?线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树 ...

  3. 线段树(SegmentTree)基础模板

    线段树模板题来源:https://www.lintcode.com/problem/segment-tree-build/description 201. 线段树的构造 /*** Definition ...

  4. 蒟蒻的线段树入门模板笔记

    线段树适合处理那些问题? 线段树是算法竞赛中常用的用来维护 区间信息 的数据结构. 线段树可以在O(logN)的时间复杂度内实现单点修改.区间修改.区间查询(区间求和,求区间最大值,求区间最小值,求区 ...

  5. 线段树SegmentTree

    什么是线段树,它能解决什么样的问题?

  6. 基尔霍夫矩阵矩阵树定理学习笔记

    背景: 好多东西没学. 勇士被快船惊天大逆转!!! 快船NBNBNB. 紧接着下午打球水杯被搞烂了......... 正题: Part1Part1Part1行列式: 对于一个n∗nn*nn∗n的矩阵A ...

  7. 回文树 PAM 学习笔记

    摘自 <2017 年国家集训队论文>" 回文树及其应用 " 一些规定 :Σ\SigmaΣ 表示字符集大小 串 SSS 和字符 ccc,用 ScScSc 表示将 ccc ...

  8. 洛谷4455 [CQOI2018]社交网络 (有向图矩阵树定理)(学习笔记)

    sro_ptx_orz qwq算是一个套路的记录 对于一个有向图来说 如果你要求一个外向生成树的话,那么如果存在一个\(u\rightarrow v\)的边 那么\(a[u][v]--,a[v][v] ...

  9. 数据结构专题-学习笔记:李超线段树

    数据结构专题 - 学习笔记:李超线段树 1. 前言 2. 详解 3. 应用 4. 总结 5. 参考资料 1. 前言 本篇博文是博主学习李超线段树的学习笔记. 2020/12/21 的时候我在 线段树算 ...

  10. 数据结构学习笔记(3-5):树

    附录:所有blog的链接 数据结构学习笔记(1):基本概念 数据结构学习笔记(2):线性结构 数据结构学习笔记(3-5):树 数据结构学习笔记(6-8):图 数据结构学习笔记(9-10):排序 数据结 ...

最新文章

  1. Symfony2Book16:Symfony2内部02-内核
  2. python资本市场财务数据分析_Python对股票财务数据进行可视化分析
  3. 实现权限控制_Spring自定义注解+AOP实现权限控制
  4. 【poj2464】树状数组
  5. liunx -bash:ls:command not found,执行命令总是报找不到
  6. Wine 0.9.37
  7. “暖心”腊八节开启中国年 全民喝粥“讨彩头”
  8. 关键词词云怎么做_如何生成关键词云图?
  9. 仿网易评论盖楼PHP+Mysql实现
  10. Android 图片查看器选择器 PictureSelector
  11. css(五)项目实战,ps切图
  12. Oracle数据库(二) 表空间的管理
  13. antV报错ResizeObserver loop limit exceeded
  14. 【学习番外篇】Firefly ROC-RK3328-CC刷Ubuntu18.04+VNC
  15. 红黑树 之 原理和算法详细介绍
  16. DevpTips_JupyterNotebook的基本命令IPython
  17. Docker部署Jenkins服务
  18. 关于shader的内存占用分析,以urp项目内置shader Lit为例
  19. 小型、低成本、低功耗的一次性收音机。第1部分:发射机
  20. IJKPlayer问题集锦之不定时更新

热门文章

  1. 基于node.js的express使用mysql语句在插入数据时防重插入
  2. 三十、K8s供应链安全1-准入控制器
  3. Linux常用命令介绍(一)——文件与文件夹操作相关命令
  4. Linux中如何针对用户及组设置磁盘配额
  5. MetaMask/metamask-extension/mascara 的运行实现
  6. Docker 安装常用软件记录
  7. linux磁盘空间用满的处理方法
  8. (未完毕)电子设计省赛--2013年国赛题
  9. JavaScript学习笔记——运算符和表达式
  10. 用python完成图形输出设备_用 Python 在多个输出设备上播放多个声音文件