线段树的懒标记与应用

news2025/1/15 22:40:53

目录

一、前言

二、Lazy-tag技术

1、update() 中的lazy-tag

三、例题

1、区间修改、区间查询(lanqiaoOJ 1133)


一、前言

本文主要讲了线段树的Lazy-tag技术和一道例题,建议自己要多练习线段树的题目。 

二、Lazy-tag技术

  • 背景:区间修改
  • 简单区间修改,例如对一个数列的 [L, R] 区间内每个元素统一加上 d。
  • 如果在线段树上用 “单点修改” 一个个修改这些元素,一次区间修改O(nlogn),还不如直接暴力修改的 O(n)。
  • 解决办法,利用线段树的特征:线段树的结点 tree[i],记录了 i 这个区间的值。
  • 再定义一个 tag[i],用它统一记录 i 这个区间的修改,而不是一个个修改区间内的每个元素。
  • 这个办法被称为 “lazy-tag”。一次区间修改 O(logn)。
  • lazy-tag (懒惰标记,或者延迟标记)。
  • 修改一个线段区间时,只对这个线段区间进行整体修改 (把修改记录在子树的根结点上),其内部每个元素的内容先不做修改。
  • 当这个线段区间的一致性被破坏时,才把变化值传递给下一层。
  • 复杂度:每次区间修改的复杂度是 O(logn) 的。

1、update() 中的lazy-tag

例:把 [4, 9] 区间内的每个元素加 3:

1)左子树递归到结点 5,即区间 [4, 5],完全包含在 [4, 9] 内,打标记 tag[5] = 3,更新 tree[5] 为 20,不再继续深入;

2)左子树递归返回,更新 tree[2] 为 30;

3)右子树递归到结点 6,即区间 [6, 8],完全包含在 [4, 9] 内,打标记 tag[6]=3,更新 tree[6] 为 23。

4)右子树递归到结点 14,即区间 [9, 9],打标记 tag[14]=3,更新 tree[14]=13;

5)右子树递归返回,更新 tree[7]=20;继续返回,更新 tree[3]=43;

6)返回到根结点,更新 tree[1]=73。

【多次修改】

  • 如果发生多次修改同一个结点上的 tag 会发生多次修改,导致冲突。
  • 例如做 2 次区间修改,一次是 [4, 9],一次是 [5, 8],它们都会影响 5:[4, 5] 这个结点。

第一次修改 [4, 9] 覆盖了结点 5,用 tag[5] 做了记录;

第二次修改 [5, 8] 不能覆盖结点 5,需要再向下搜到结点 11:[5, 5],从而破坏了 tag[5],此时原 tag[5] 记录的区间统一修改就不得不往它的子结点传递和执行了,传递后 tag[5] 失去了用途,需要清空。

【push_down】

lazy-tag 的主要操作是解决多次区间修改的冲突,用 push_down() 函数完成。它首先检查结点 p 的 tag[p],如果有值,说明前面做区间修改时给 p 打了 tag 标记,接下来就把 tag[p] 传给左右子树,然后把 tag[p] 清零。push_down() 函数不仅在 “区间修改” 中用到,在 “区间查询” 中同样用到。

三、例题

1、区间修改、区间查询(lanqiaoOJ 1133)

注:该题用树状数组讲过

【题目描述】

给定一个长度为 N 的数组 a,初值为 a1, a2, ..., aN

有 Q 个操作,操作有两种:

1 L R d:将区间 [L,R] 内每个数加上 d。

2 L R:输出区间 [L, R] 内每个数的和。

【输入描述】

第 1 行是整数 N、M,表示数组 a 的长度和操作个数。1<=n, m<=10^5

第 2 行包含 N 个非负整数 a1, a2, ..., aN,表示数组 a 的初值

第 3~M-2 行每行表示一个操作

【输出描述】

输出每行一个整数,表示查询的答案

【输入样例】

5 5

1 2 3 4 5

2 1 2

1 2 3 1

2 1 3

1 1 5 1

2 1 5

【输出样例】

3

8

22

【完整代码】

def bulid(p,pl,pr):     #建树
    if pl==pr:
        tree[p]=a[pl]
        return
    mid=(pl+pr)>>1
    bulid(p<<1,pl,mid)
    bulid(p<<1|1,mid+1,pr)
    tree[p]=tree[p<<1]+tree[p<<1|1]     #push_up(p)

def addtag(p,pl,pr,d):       #给结点p打tag标记,并更新tree
    tag[p]+=d               #打上tag标记
    tree[p]+=d*(pr-pl+1)    #计算新的tree

def push_down(p,pl,pr):
    if tag[p]>0:            #有tag标记,这是以前做区间修改时留下的
        mid=(pl+pr)>>1
        addtag(p<<1,pl,mid,tag[p])      #把tag标记传给左子树
        addtag(p<<1|1,mid+1,pr,tag[p])  #将tag标记传给右子树
        tag[p]=0                        #p自己的tag被传走了

def update(L,R,p,pl,pr,d):
    if L<=pl and pr<=R:     #在这个区间里面
        addtag(p,pl,pr,d)
        return
    push_down(p,pl,pr)  #将懒惰标记传递给孩子.tree[p]不能完全被包含在需要修改的区间[L,R]中,需要解决多次修改的冲突问题
    mid=(pl+pr)>>1
    if L<=mid:
        update(L,R,p<<1,pl,mid,d)
    if R>=mid+1:
        update(L,R,p<<1|1,mid+1,pr,d)
    tree[p]=tree[p<<1]+tree[p<<1|1]     #push_up(p)

def query(L,R,p,pl,pr):
    if L<=pl and pr<=R:
        return tree[p]
    push_down(p,pl,pr)
    res=0
    mid=(pl+pr)>>1
    if L<=mid:
        res+=query(L,R,p<<1,pl,mid)
    if R>mid:
        res+=query(L,R,p<<1|1,mid+1,pr)
    return res

n,m=map(int,input().split())
a=[0]+list(map(int,input().split()))
tag=[0]*(len(a)<<2)      #4倍大
tree=[0]*(len(a)<<2)
bulid(1,1,n)            #建树
for i in range(m):
    w=list(map(int,input().split()))
    if len(w)==3:       #区间查询:[L,R]的区间和
        q,L,R=w
        print(query(L,R,1,1,n))
    else:               #区间修改:把[L,R]的每个元素加上d
        q,L,R,d=w
        update(L,R,1,1,n,d)

1)build()建树

2)update() 函数更新区间的值,把区间内所有元素的值加上 d。如果 tree[p] 这棵子树完全被包含在需要修改的区间 [L, R] 中,只需要对根 tree[p] 打上标记即可,不用修改 p 的子结点。

3)addtag() 打标记

4)update() 中的 push_down。update() 函数更新区间的值,把区间内所有元素的值加上 d。如果 tree[p] 这棵子树不能完全被包含在需要修改的区间 [L,R] 中,需要解决多次修改的冲突问题,用 push_down() 实现。

解决 tag 的冲突问题。把 tag 分别传给左右子树。

注意:tag 应该持续向下传递,直到能覆盖区间为止。但是 push_down() 只向下传递了一次。因为使用 push_down() 的 update() 是个递归函数,update() 会在递归时一层层地用 push_down() 来传递 tag。

5)query() 查询区间和。查询时用到 tag,也就是用 push_down() 来处理 tag。

【总结】

线段树是一个综合的知识点,它能锻炼以下能力:

1)计算理论:二分法、二叉树。

2)逻辑思维:懒惰标记技术 lazy-tag。

3)代码能力:递归。

请大量做线段树的练习,对线段树的掌握说明进入了算法竞赛的大门。而且线段树的扩展应用极多,可阅读资料了解。

【习题】

 以上,线段树的懒标记与应用

祝好

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

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

相关文章

水面漂浮物垃圾识别检测系统 YOlOv7

水面漂浮物垃圾识别检测系统通过PythonYOLOv7网络模型&#xff0c;实现对水面漂浮物以及生活各种垃圾等全天候24小时不间断智能化检测。Python是一种由Guido van Rossum开发的通用编程语言&#xff0c;它很快就变得非常流行&#xff0c;主要是因为它的简单性和代码可读性。它使…

Linux- 系统随你玩之--文本处理三剑客-带头一哥-awk

文章目录1、awk概述2、awk原理2.1、 awk 工作原理2.2、 与sed工作原理比较2.3、 awk与sed的区别3、使用方法及原理3.1、格式如下&#xff1a;3.2、 匹配规则3.3、 参数说明3.4、处理规则与流程控制3.5、 常用 awk 内置变量3.6、 awk 正则表达式解释4、操作实例4.1、 准备工作4.…

(十七)抽象队列同步器AQS

AQSAbstractQueuedSynchronizer抽象同步队列简称AQS&#xff0c;它是实现同步器的基础组件&#xff0c;并发包中锁的底层就是使用AQS实现。类图如下&#xff0c;AbstractQueuedLongSynchronizer与AbstractQueuedSynchronizer结构一模一样&#xff0c;只是AbstractQueuedSynchro…

Springboot+java师生交流答疑作业系统

&#xff0c;本系统拥有学生&#xff0c;教师&#xff0c;管理员三个角色&#xff0c;学生可以注册登陆系统&#xff0c;查看新闻&#xff0c;查看教学&#xff0c;在线提问答疑&#xff0c;提交作业&#xff0c;发布交流&#xff0c;留言反馈等功能&#xff0c;教师可以发布教…

恶意代码分析实战 14 反虚拟机技术

14.1 Lab17-01 题目 这个恶意代码使用了什么反虚拟机技术&#xff1f; 恶意代码用存在漏洞的x86指令来确定自己是否运行在虚拟机中。 如果你有一个商业版本IDAPro&#xff0c;运行第17章中代码清单17-4所示的IDAPython脚本&#xff08;提供如jindAniM.py&#xff09;&#…

spring boot前后端交互之数据格式转换

在前后端分离开发的项目种&#xff0c;前端获取数据的方式基本都是通过Ajax。请求方法也有所不同,常见的有POST,GET,PUT,DELETE等。甚至连请求的数据类型都不一样&#xff0c;x-www-form-urlencodeed,form-data,json等。 那么在前后端交互过程中&#xff0c;具体的数据该如何接…

ESP32设备驱动-8x8LED点阵驱动(基于Max7219+SPI)

8x8LED点阵驱动(基于Max7219+SPI) 1、Max7219介绍 MAX7219/MAX7221是紧凑型串行输入/输出共阴极显示驱动器,可将微处理器(Ps)连接到多达8位的7段数字LED显示器、条形图显示器或64个独立LED。片上包括一个 BCD 代码 B 解码器、多路扫描电路、段和数字驱动器,以及存储每个数字…

通信电子、嵌入式类面试题刷题计划04

文章目录036——看门狗电路的作用是什么&#xff1f;【社招】037——你了解CAN总线协议吗&#xff1f;说一说你的理解【社招】038——锁存器、触发器、寄存器三者的区别&#xff1f;【校招】039——D触发器和D锁存器的区别是什么&#xff1f;【校招】040——三极管和MOS管的区别…

Cadence PCB仿真使用Allegro PCB SI生成单网络EMI报告Single Net EMI Report及报告导读图文教程

🏡《Cadence 开发合集目录》   🏡《Cadence PCB 仿真宝典目录》 目录 1,概述2,生成报告3,报告导读4,总结1,概述 单网络EMI报告是值将差分模式下的网络视为单个网络,分析来自时钟上升沿的辐射影响。本文简单介绍使用Allegro PCB SI生成单网络EMI报告的方法,及Singl…

搜索引擎位置跟踪应用SerpBear

什么是 SerpBear ? SerpBear 是一款开源搜索引擎位置跟踪应用程序。它允许你跟踪你的网站在谷歌中的关键词位置&#xff0c;并得到他们的位置通知。 软件特点&#xff1a; 无限关键词&#xff1a;添加无限域名和无限关键词以跟踪其 SERP电子邮件通知&#xff1a;每天/每周/每…

车载以太网简介

车载以太网简介 基本概念 传统车载网络 LIN&#xff1a;用于通信速率低的场景&#xff0c;比如车窗、座椅等。CAN&#xff1a;目前车载网络首先&#xff0c;低成本高可靠。FlexRay &#xff1a;具备故障容错的车载总线系统。MOST&#xff1a;内置流媒体数据信道&#xff0c;…

2023年企业信息安全缺陷和解决方案,防止职员外泄信息

随着网络的发展和普及&#xff0c;信息安全与每个人息息相关&#xff0c;包含方方面。每个人既是独立个体又必须和社会交换资源。这就需要把控一个尺度。 要了解信息安全&#xff0c;首先需要对信息有个大体了解。从拥有者和使用者分类分为&#xff0c;个人&#xff0c;企业&a…

恶意代码分析实战 11 恶意代码的网络特征

11.1 Lab14-01 问题 恶意代码使用了哪些网络库&#xff1f;它们的优势是什么&#xff1f; 使用WireShark进行动态分析。 使用另外的机器进行分析对比可知&#xff0c;User-Agent不是硬编码。 请求的URL值得注意。 回答&#xff1a;使用了URLDownloadToCacheFileA函数&#…

JavaEE多线程-定时器

目录一、定时器1.1 什么是定时器&#xff1f;1.2 定时器的构成二、简单实现定时器一、定时器 1.1 什么是定时器&#xff1f; 定时器是多线程编码中的一个重要组件,它就好比一个闹钟,例如我们想去坐车,但是不想现在去坐车,想8:30去坐车,于是我们订了一个8点钟的闹钟,也就是说定…

Linux内核驱动初探(四) 内部看门狗

目录 0. 前言 1. menuconfig 2. 设备树 3. 拓展试验 0. 前言 这次的内部看门狗驱动也比较顺利&#xff0c;重点看了 原理图和4.19.x 内核的配置。 内部看门狗设备名叫做 /dev/watchdog 。 1. menuconfig 我们在 linux-menuconfig 里面如下设置&#xff1a;进入 Device D…

[Java]JavaWeb学习笔记(动力节点老杜2022)

文章目录&#x1f97d; Tomcat服务器&#x1f30a; 下载与安装&#x1f30a; 关于Tomcat服务器的目录&#x1f30a; 启动Tomcat&#x1f30a; 实现一个最基本的web应用&#xff08;这个web应用中没有java小程序&#xff09;&#x1f97d; 静态资源与动态资源&#x1f97d; 模拟…

GPU虚拟化(留坑)

文章内容大程度参考B站王利明老师对《GPU虚拟化技术分享》的演讲&#xff1a;https://b23.tv/uQKBpcK GPU 有什么用&#xff1f; GPU可以用于图形渲染&#xff0c;也能够用于高性能计算和编解码等场景。 图&#xff1a;GPU 的典型软件架构&#xff08;不含虚拟化&#xff09; …

注解存储对象到Spring,详解 五大类注解 和方法注解

上一篇博客我们介绍了如何使用xml来引入bean对象&#xff0c;当项目多的时候&#xff0c;显然那样是比较麻烦的。现在我们只需要 个注解就可以替代了。注意&#xff1a;注解和xml可以同时使用准备工作:配置扫描路径我们需要配置 下存储对象的扫描包路径&#xff0c;只有被配置的…

【笔记】openwrt - full cone NAT(全锥NAT)、解决“arp_cache: neighbor table overflow!”

最近安装了比特彗星&#xff08;bitcomet&#xff09;后&#xff0c;老是收到警告说日志的接收超过每秒上限了。一看日志&#xff0c;好家伙&#xff0c;一堆的kern.info kernel: [194004.157620] neighbour: arp_cache: neighbor table overflow!日志&#xff0c;还是kernel的…

损失函数总结

回归损失与分类损失 回归用于逼近某个数值,预测的结果是连续的,例如预测小明的身高,160,161,162,163cm。平方损失即MSE: 分类用于预测物体属于某一个标签,预测的结果是离散的,例如预测明天是否下雪:是or否。 由于预测分类,最终的输出需要介于(0,1)之间,通常在网络…