线段树(重要!多加理解懒惰标记!)

news2024/9/21 7:04:52

基础概念:

线段(区间)[L,R] 所对应的线段树是由区间 [L,R] 及其子区间构成的二叉树(如下图所示)

线段树具有的特性: 

(1)线段树的叶结点为只有一个元素的区间,因此长度为 n 的区间所对应的线段树有 n 个叶结点;

(2)在构造线段树时,将每一个区间分成两个部分,且两部分的长度之差不超过1,因此线段树的叶结点出现在最后一层或倒数第二层,由此可得,线段树的高度不超过[\log_{2}n ]+1

线段树近似于满二叉树,因此可以用二叉树的数组表示法表示线段树。对于长度为n的区间,由于对应线段树的高度为[\log_{2}n ]+1,由(2)可知,在用数组表示线段树时,数组的大小的上界为

                2^{[\log_{2}n]+1} - 1 < 2^{[\log_{2}n]+1+1} = 2^{\log_{2}n}*2^{2} = 4n

即数组的大小不超过4n。另外,线段树的每一个结点表示一个区间,线段树结点中所存储的数据值为该区间的和。

线段树的主要功能 

线段树适用于与区间统计有关的问题,如果需要对区间内的数据进行动态更新,而且需要进行区间查询,那么使用线段树可以达到较快的更新和查询速度。 

线段树的更新 

单点更新:

改变一个元素的值即改变叶结点的值,则从叶结点到根结点的所有结点的和都要更新。

区间更新:

 

 

 lazy标记就好像父亲给儿子代管零花钱。当我们做懒惰修改,当所修改区间完全覆盖子节点区间,先修改该子节点区间的sum值,再打上一个lazy标记,然后立刻返回,不用管下面孩子的更新。等到下载需要时,父节点再下传lazy标记(零花钱)。

 完整代码展示:

1.数组类型定义:

#include<iostream>
#include<map>
#include<stack>
using namespace std;

//线段树近似于满二叉树,因此可以用二叉树的数组表示法表示线段树。
constexpr auto N = 1000;
//sum[i]为结点i所对应区间中元素的和,初始值为0;lazy[i]为结点i的懒惰结点(延时更新值)
//在后面线段树操作中采用延时更新时使用该数组,初始值为0
int sum[N << 2], lazy[N << 2];

//根据其类似二叉树的性质,由自底向上的方法构造线段树
//当一个结点的孩子更新完毕后,需要更新该结点到根结点上所有结点的值,即需要更新当前结点的信息,成为向上更新函数
void pushUp(int rt) {
	sum[rt] = sum[rt << 1] + sum[rt << 1 | 1]; 
}
//根据数组a构建表示区间和线段树,rt表示当前结点编号,l,r表示当前结点区间
//该算法类似于后根遍历
void build(int a[N], int rt, int l, int r) {
	if (l == r) { //达到叶结点
		sum[rt] = a[l]; //叶结点中只有一个元素,元素和即为该元素的值
		return;
	}
	int mid = (l + r) >> 1;
	build(a, rt << 1, l,mid); //构建左子树
	build(a, (rt << 1) | 1, mid + 1, r); //构建右子树,这里(rt << 1) | 1相当于rt=2*rt+1;
	pushUp(rt); //根据孩子结点信息更新当前结点的信息
}

/*
	线段树的更新有两种类型:单点更新(改变一个元素的值)和区间更新(改变一个下标区间内所有元素的值)
	单点更新:
		改变一个元素的值即改变叶结点的值,则从叶结点到根结点的所有结点的和都要更新。
*/
//单点更新,将a[pos]的值增加inc    (递归+值更新)
void pointUpd(int rt, int l, int r, int pos, int inc) {
	if (l == r) {  //达到叶结点,此时l=r=pos
		sum[rt] += inc; //修改叶结点的值
		return;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid)pointUpd(rt << 1, l, mid, pos, inc);//pos在左子树
	else pointUpd(rt << 1 | 1, mid + 1, r, pos, inc);//pos在右子树
	pushUp(rt); //更新当前结点到根节点的所有结点
}

//将结点rt的延时更新值向下传递。参数ln、rn分别为结点rt的左子树和右子树所对应区间的长度
void pushDown(int rt, int ln, int rn) {
	if (lazy[rt]) {
		lazy[rt << 1]+=lazy[rt]; //当前结点延迟更新值传递给左孩子
		lazy[(rt << 1) | 1] += lazy[rt]; //当前结点延迟更新值传递给右孩子
		//利用当前结点rt的延时更新值修改rt的孩子结点的sum
		sum[rt << 1] += lazy[rt] * ln;
		sum[rt << 1 | 1] += lazy[rt] + rn;
		lazy[rt] = 0; //清除本节点的延时更新值
	}
}
//区间更新,将a在区间[L,R]中的元素值都增加inc
//由于采用延时更新,因此在更新到达终止节点就不在向下更新
void rangeUpd(int rt, int l, int r, int L, int R, int inc) {
	if (L <= l && r <= R) {  //包含,结点[l,r]为中直接点
		sum[rt] += inc * (r - l + 1); //更新当前区间的和,当前区间的每一个元素都增加inc
		lazy[rt] += inc; //更新当前结点的延时更新值
		return;
	}
	int mid = (r + l) >> 1;
	pushDown(rt, mid - l + 1, r - mid); //更新当前结点的sum,并将延时更新值向孩子结点传递
	//判断左右子树与[L,R]有无交集,若有交集则继续向下搜索
	if (L <= mid) rangeUpd(rt<<1,l,mid,L,R,inc);
	if (R > mid)rangeUpd((rt << 1) | 1, mid + 1, r, L, R, inc);
	pushUp(rt); //更新当前结点到根结点的所有结点

}

/*
	查询方法:
	从根节点出发对线段树进行先根遍历,如果遇到终止节点,则可以直接返回该结点的sum,
	最终结果为所有终止节点的sum的和。
	注意在对一个结点进行处理前需要嗲用函数pushDown将该节点的延时值向下传递
*/
//线段树的查询操作,查询下标区间L~R中元素的和,结果作为函数的返回值
int query(int rt, int l, int r, int L, int R) {
	if (L <= l && r <=R) //判断结点l~r为终止节点
		return sum[rt];
	int mid = (l + r) >> 1;
	pushDown(rt, mid - l + 1, r - mid); //更新当前结点的sum,并将延时更新值向孩子结点传递
	int ret = 0; 
	if (L <= mid) ret += query(rt << 1, l, mid, L, R);
	if (R > mid)ret += query((rt << 1) | 1, mid + 1, r, L, R);
	return ret;
}


int main() {
	int a[] = { 2,5,3,4,1,6,8,9,7,3 };
	build(a, 1, 0, 9);
	pointUpd(1, 0, 9, 7, 5);
	rangeUpd(1, 0, 9, 2, 5, 5);
	cout<<query(1, 0, 9, 0,9);
}

2.结构体定义

#include<iostream>
using namespace std;

const int maxn = 1e5;
typedef long long ll;

struct node {
    ll left, right; //左右端点。
    ll sum;//区间[left,right]的和
    ll lazy;//lazy标记。
}SegTree[maxn << 2];

//建树和普通线段树是没有什么区别的
void BuildTree(int rt, int l, int r) {
    SegTree[rt].left = l, SegTree[rt].right = r;
    SegTree[rt].lazy = 0;
    if (l == r) {
        cin >> SegTree[rt].sum;//赋值。
        return;
    }
    BuildTree(rt << 1, l, (l + r) >> 1);   //递归建立左子树
    BuildTree(rt << 1 | 1, ((l + r) >> 1) + 1, r);//递归建立右子树。
    SegTree[rt].sum = SegTree[rt << 1].sum + SegTree[rt << 1 | 1].sum;
}

//Pushdown函数(lazy标记下移)
void PushDown(int rt) {
    //rt代表要下移的父结点。
    if (SegTree[rt].lazy) {
        //如果是有标记的。
        SegTree[rt << 1].lazy += SegTree[rt].lazy;//标记下移给左孩子。
        SegTree[rt << 1 | 1].lazy += SegTree[rt].lazy;//标记下移给右孩子。
        //左右孩子将欠下的给补上。
        SegTree[rt << 1].sum += (SegTree[rt << 1].right - SegTree[rt << 1].left + 1) * SegTree[rt].lazy;
        SegTree[rt << 1 | 1].sum += (SegTree[rt << 1 | 1].right - SegTree[rt << 1 | 1].left + 1) * SegTree[rt].lazy;
        SegTree[rt].lazy = 0;//把欠的给清除。
    }
}

//Update更新函数
void UpDate(int rt, int c, int l, int r) {
    //对区间修改的函数也同样可以对单点进行修改。
    if (SegTree[rt].left == l && SegTree[rt].right == r) {
        SegTree[rt].lazy += c; //记下标记,
        SegTree[rt].sum += (SegTree[rt].right - SegTree[rt].left + 1) * c;
        return;          //停止递归。
    }
    if (SegTree[rt].left == SegTree[rt].right) {
        //到了叶子结点,不能往下了,也返回。
        return;
    }
    PushDown(rt);//到了这步发现区间没有全覆盖我们自然要先标记下移,再去寻找左右孩子
    int mid = (SegTree[rt].left + SegTree[rt].right) / 2;
    if (r <= mid) {
        //更新区间全部在左孩子。
        UpDate(rt << 1, c, l, r);
    }
    else if (l > mid) {
        //更新区间全部在右孩子。
        UpDate(rt << 1 | 1, c, l, r);
    }
    else {
        //否则左右区间都有。
        UpDate(rt << 1, c, l, mid); //更新左孩子
        UpDate(rt << 1 | 1, c, mid + 1, r); //更新右孩子。
    }
    SegTree[rt].sum = SegTree[rt << 1].sum + SegTree[rt << 1 | 1].sum;
}

//QueryTree函数(区间查询)
ll QueryTree(int rt, int l, int r) {
    //区间查询,对[l,r]区间查询
    if (SegTree[rt].left == l && SegTree[rt].right == r) {
        return SegTree[rt].sum;
    }
    PushDown(rt);
    int mid = (SegTree[rt].right + SegTree[rt].left) >> 1;
    ll ans = 0;
    if (r <= mid) {
        //说明全部在左孩子
        ans += QueryTree(rt << 1, l, r);
    }
    else if (l > mid) {
        //说明全部在右孩子
        ans += QueryTree(rt << 1 | 1, l, r);
    }
    else {
        ans += QueryTree(rt << 1, l, mid);
        ans += QueryTree(rt << 1 | 1, mid + 1, r);
    }
    return ans;
}

int main() {
    int n, m;
    while (cin >> n >> m) {
        BuildTree(1, 1, n);
        while (m--) {
            string op;
            int a, b, c;
            cin >> op;
            if (op == "Q") {
                cin >> a >> b;
                cout << QueryTree(1, a, b) << endl;
            }
            else {
                cin >> a >> b >> c;
                UpDate(1, c, a, b);
            }
        }
    }
    return 0;
}

拓展:

线段树 从入门到进阶(超清晰,简单易懂)_繁凡さん的博客-CSDN博客_线段树进阶https://blog.csdn.net/weixin_45697774/article/details/104274713?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167413366516800217086477%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167413366516800217086477&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-104274713-null-null.142%5Ev71%5Econtrol_1,201%5Ev4%5Eadd_ask&utm_term=%E7%BA%BF%E6%AE%B5%E6%A0%91&spm=1018.2226.3001.4187

逻辑与(&&)、逻辑或(||)、按位与(&)、按位或(|)、按位异或(^)、按位取反(~)_Aczy156的博客-CSDN博客_按位同或https://blog.csdn.net/qq_43345204/article/details/92794251?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167418008616800192278342%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167418008616800192278342&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_positive~default-1-92794251-null-null.142%5Ev71%5Econtrol_1,201%5Ev4%5Eadd_ask&utm_term=%E6%8C%89%E4%BD%8D%E5%BC%82%E6%88%96&spm=1018.2226.3001.4187
线段树进阶之延迟标记 (~详细整理)区间修改_unique_pursuit的博客-CSDN博客https://pursuit.blog.csdn.net/article/details/107880933?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2-107880933-blog-79921997.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-2-107880933-blog-79921997.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=5 

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

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

相关文章

最新版海豚调度dolphinscheduler-3.1.3安装部署详细教程

0 背景 本文基于Ambari集群搭建最新版本的海豚调度dolphinscheduler-3.1.3版本&#xff0c;后续会尝试整合到Ambari中。 1 安装准备 安装dolphinscheduler需要在环境中安装如下依赖 ① JDK8 下载JDK (1.8)&#xff0c;安装并配置 JAVA_HOME 环境变量&#xff0c;并将其下的 …

用 22 张照片打开 23 年

魔幻又带有现实主义色彩的三年似乎终将见底。这也为 2023 年赋予了一些新的意义&#xff0c;或许是充满生机、怀揣希望、满怀爱意&#xff0c;或许是重新启航、步履不停、勇敢探索……为此&#xff0c;我们收集了 22 位社区用户和公司小伙伴在过去一年的「特别 Moment」及新年愿…

你认为DAO是否可行?新年计划,卯足干劲,兔必No.1

文章目录&#x1f31f; 课前小差&#x1f31f; 聚沙成塔&#x1f31f; 社会价值&#x1f31f; DAO是什么&#x1f31f; 国产化&#x1f31f; 商业化回报&#x1f31f; 写在最后&#x1f31f; 课前小差 哈喽&#xff0c;大家好&#xff0c;我是几何心凉&#xff0c;这是一份全新…

Spring高级之BeanFactory功能

首先&#xff0c;我们想要知道一个接口有哪些功能&#xff0c;就必须要看这个接口的源代码&#xff0c;在idea中&#xff0c;选中这个接口CtrlF12&#xff0c;来查看这个接口里面有哪些方法&#xff1a; 表面上来看&#xff0c;功能其实很少&#xff0c;查看源码及其方法功能 …

来吧,Jenkins+git+mvn+shell一键部署实践起来

环境&#xff1a;centosJenkins-2.319系统自带gitmvn3.8.7jdk1.8一、安装jdk1、https://blog.csdn.net/codedz/article/details/124044974centos自带了openjdk&#xff0c;我是选择自己重新搞一个&#xff0c;用的上面链接地址的yum安装方式2、安装完成查看版本查看java安装路径…

优思学院|质量人对控制图中的规格线和控制线傻傻分不清?

质量人、六西格玛[1]人和很多不同类型的工程师都需要了解什么是控制图&#xff0c;而在控制图中的规格限制&#xff08;Specification Limit&#xff09;"和"控制限制&#xff08;Control Limit&#xff09;"原来对好多人来说都是傻傻分不清&#xff01; 规格限…

线段树入门

对于一个区间进行询问&#xff0c;进行修改&#xff0c;都是用线段树进行处理。线段树和普通的树不一样&#xff0c;普通的树的节点存的是一个编号&#xff0c;线段树存的是一个区间&#xff0c;而且线段树一定是一棵完全二叉树。例如&#xff1a;这就是一棵线段树。例如对于[1…

【Ajax】数据交换格式XML 和 JSON

一、什么是数据交换格式数据交换格式&#xff0c;就是服务器端与客户端之间进行数据传输与交换的格式。前端领域&#xff0c;经常提及的两种数据交换格式分别是 XML 和 JSON。其中 XML 用的非常少&#xff0c;所以&#xff0c;我们重点要学习的数据交换格式就是 JSON。二、XML1…

让交互更加生动!巧用CSS实现鼠标跟随 3D 旋转效果

简单分析一下&#xff0c;这个交互效果主要有两个核心&#xff1a; 借助了 CSS 3D 的能力 元素的旋转需要和鼠标的移动相结合 本人简单的说一下如何使用纯 CSS 实现类似的交互效果&#xff0c;以及&#xff0c;借助 JavaScript 绑定鼠标事件&#xff0c;快速还原上述效果。 …

数据结构---set篇

第一次超时是因为用memsetmemsetmemset不得不超时&#xff0c;第二次超时是我用vectorvectorvector数组的时候&#xff0c;然后以O(n)O(n)O(n)复杂度查找元素之后使用eraseeraseerase方法进行删除&#xff0c;第三次超时是我把查找元素改成了O(logn)O(logn)O(logn)之后用vector…

epoll的ET和LT模式

简介 epoll对fd的操作有两种模式&#xff1a;LT(Level Trigger&#xff0c;水平触发)模式&#xff0c;和ET&#xff08;Edge Trigger,边缘触发&#xff09;模式。 LT 模式是默认的工作模式&#xff0c;这种模式下&#xff0c;epoll相当于一个效率较高的poll&#xff1b; ET模…

89. 注意力机制以及代码实现Nadaraya-Waston 核回归

1. 心理学 动物需要在复杂环境下有效关注值得注意的点心理学框架&#xff1a;人类根据随意线索和不随意线索选择注意点 随意&#xff1a;随着自己的意识&#xff0c;有点强调主观能动性的意味。 2. 注意力机制 2. 非参注意力池化层 3. Nadaraya-Waston 核回归 4. 参数化的注意…

Downie4.6.4视频下载工具

前言 Downie是Mac下一个简单的下载管理器&#xff0c;可以让您快速将不同的视频网站上的视频下载并保存到电脑磁盘里然后使用您的默认媒体播放器观看它们。 下载 Downie 解压后直接安装 主要特点 支持许多网站目前支持超过1,000个不同的网站&#xff08;包括YouTube&#…

Linux | 浅谈Shell运行原理【王婆竟是资本家】

文章目录&#x1f4a7;Shell的运行原理&#x1f449;Shell的基本概念与作用&#x1f449;原理的展示与剖析&#x1f449;Shell外壳感性理解【一门亲事】&#x1f4a7;总结&#x1f4a7;Shell的运行原理 &#x1f449;Shell的基本概念与作用 Linux严格意义上说的是一个操作系统…

华为数字化转型之道 平台篇 第十三章 变革治理体系

第十三章 变革治理体系 约翰科特在《领导变革》一书中说:“变革的领导团队既需要管理能力,也需要领导能力,他们必须结合起来。 前面我们也谈到,数字化转型不仅是技术的创新,更是一项系统工程和企业真正的变革。企业要转型成功,既需要各个组织的积极参与和通力合作,又不…

深度学习中高斯噪声:为什么以及如何使用

在数学上&#xff0c;高斯噪声是一种通过向输入数据添加均值为零和标准差(σ)的正态分布随机值而产生的噪声。 正态分布&#xff0c;也称为高斯分布&#xff0c;是一种连续概率分布&#xff0c;由其概率密度函数 (PDF) 定义&#xff1a; pdf(x) (1/ (σ*sqrt(2*π))) *e^(- (x…

Task6:文本函数查找函数

文章目录一 文本函数1 Text函数2 mid函数3 replace函数二 查找函数1 Vlookup2 Xlookup一 文本函数 1 Text函数 作用&#xff1a;将数值转换为指定格式的文本 语法&#xff1a;TEXT(value,format_text) &#xff08;1&#xff09;转换为大写 消费日期转换为大写 TEXT(A2,”[DB…

第五届字节跳动青训营 前端进阶学习笔记(六)什么才是好的JavaScript代码

文章目录前言问题引入实现一个交通信号灯的状态切换1.基本实现2.状态封装实现3.职责分离实现求一个数是否是4的幂1.基本实现3.数学优化洗牌算法1.基本实现2.均匀算法实现总结前言 课程重点&#xff1a; 代码规范相关事项如何优化代码 问题引入 试看下面一段代码&#xff0c…

认识UDP、TCP协议

一、Socket 首先&#xff0c;我们需要了解一下socket。 在上一篇文章当中&#xff0c;我们了解了TCP-IP五层协议模型初识网络&#xff1a;IP、端口、网络协议、TCP-IP五层模型_革凡成圣211的博客-CSDN博客TCP/IP五层协议详解https://blog.csdn.net/weixin_56738054/article/det…

Crack:RadiAnt DICOM Viewer 2023.1 BETA #1300

RadiAnt DICOM Viewer 2023.1 BETA #1300 built on January 13, 2023 New features: Length ratio calculation. Ellipsoid / bullet volume calculation. Added option to color and/or pin specific items to top in the DICOM tags window. 多式DICOM的技术支持 该软件能够打…