洛谷 P3372 【模板】线段树 1

【题目链接】

洛谷 P3372 【模板】线段树 1

【题目考点】

1. 线段树
2. 树状数组

【解题思路】

本题要求维护区间和,实现区间修改、区间查询。
可以使用树状数组或线段树完成该问题,本文仅介绍使用线段树的解法。

解法1:线段树

线段树的基础概念及性质见:洛谷 P3374 【模板】树状数组 1(线段树解法)

上题中已经给出了线段树建树的方法。本文主要介绍如何在线段树上进行区间修改与区间查询。

1. 延迟标记

原数值序列输入到a数组,叫做a序列。对a序列建立线段树。
区间修改操作为使a序列区间 [ l , r ] [l, r] [l,r]中的元素都增加数值v。区间查询为返回区间 [ l , r ] [l, r] [l,r]中元素的加和。考虑维护区间和的线段树数组应该如何变化。
(1) 延迟标记
如果线段树中结点p表示的区间被区间 [ l , r ] [l,r] [l,r]完全覆盖,则可以考虑不去修改结点p的子孙结点的值,而是直接在结点p上增加延迟标记
延迟标记,也叫懒标记(Lazy Tag),是保存在线段树结点中的一个成员变量,记为tag。

struct Node
{int l, r;long long val, tag;
} tree[4*N];

如果结点p的标记tree[p].tag大于0,表示结点p所表示的区间[tree[p].l, tree[p].r]中所有元素都应该加上tag。当前结点p的值tree[p].val已更新,但结点p的子孙结点的值都未更新。
设置addTag函数完成将结点i所表示的区间中所有元素都增加v,实际操作为在改变结点i的标记tag,同时修改结点i的值val。

void addTag(int i, long long v)
{//在地址为i的结点表示的区间[tree[i].l, tree[i].r]中每个元素增加vtree[i].tag += v;//增加标记tree[i].val += (tree[i].r-tree[i].l+1)*v;//区间[l, r]共有r-l+1个元素,每个元素增加v,共增加(r-l+1)*v。
}

(2) 标记下传
如果需要访问线段树中当前结点的子结点,则需要保证子结点的值val是正确的。
如果当前结点p的标记tag不为0,则表示对当前结点p表示的区间存在一些修改操作,而这些修改操作并没有作用到结点p的子孙结点。我们需要根据结点p的标记对结点p的子结点进行修改,而后将结点p的标记归零,这样就可以保证结点p的子结点的值就是该结点所表示区间的加和。

标记下传(pushDown):根据当前结点的标记tag更新该结点的子结点的标记tag与值val
具体步骤为:

  1. 如果当前结点标记为0,则返回。
  2. 根据当前标记更新当前结点左右孩子的值,并为左右孩子增加标记。
  3. 当前结点标记清零。
void pushDown(int i)//地址为i的结点标记下传
{if(tree[i].tag == 0)return;addTag(2*i, tree[i].tag);addTag(2*i+1, tree[i].tag);tree[i].tag = 0;
}
2. 区间修改

区间修改操作为使a序列区间 [ l , r ] [l, r] [l,r]中的元素都增加数值v。
在线段树中,访问到地址为i结点,考虑区间修改操作如何影响当前结点表示的区间对应线段树中各结点的值。

  1. 如果当前结点表示的区间和区间 [ l , r ] [l, r] [l,r]不相交,则直接返回。
  2. 如果当前结点表示的区间完全被区间 [ l , r ] [l, r] [l,r]覆盖时,只修改当前结点的标记和值,不再修改其子孙。
  3. 否则当前结点的左右孩子表示的区间都可能与区间 [ l , r ] [l, r] [l,r]有交集。在修改子孙结点前,应该先进行标记下传pushDown操作,更新孩子结点的标记和值。而后对当前结点的左右孩子进行对区间 [ l , r ] [l, r] [l,r]的区间修改操作。
  4. 当前结点左右子树的值更新结束后,再进行pushUp操作,根据左右孩子的值更新当前结点的值。

该操作与区间查询的时间复杂度相同,为 O ( log ⁡ n ) O(\log n) O(logn)

void update(int i, int l, int r, long long v)//在结点地址为i的子树中,区间[l, r]中的所有元素增加v
{if(tree[i].r < l || tree[i].l > r)return;if(l <= tree[i].l && tree[i].r <= r) {addTag(i, v);return;}pushDown(i);//注意每到达一个顶点都要标记下传update(2*i, l, r, v);update(2*i+1, l, r, v);pushUp(i);//根据孩子的值更新结点i的值
}
3. 区间查询

要查询a序列给定区间 [ l , r ] [l, r] [l,r]中元素的加和,要做的是将 [ l , r ] [l, r] [l,r]区间划分为几个线段树中结点表示的区间,将这些区间的值加和。
要在地址为i的结点表示的区间中,返回 [ l , r ] [l, r] [l,r]中元素的加和:

  1. 判断当前结点表示的区间和 [ l , r ] [l, r] [l,r]是否有交集。如果没有交集则返回。
  2. 如果当前结点表示的区间完全被区间 [ l , r ] [l, r] [l,r]包含,则返回当前结点的值。
  3. 否则需要通过访问子结点来获取区间 [ l , r ] [l, r] [l,r]中元素的加和。
    访问子结点前需要先进行标记下传pushDown操作。
    而后分别在当前结点的两个子树中查询区间 [ l , r ] [l, r] [l,r]中元素的加和,将二者的加和返回。
    区间查询的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)
    (证明见洛谷 P3374 【模板】树状数组 1(线段树解法))
//在地址为i的子树中,查询区间[l, r]中所有元素的加和
long long query(int i, int l, int r)
{    if(tree[i].r < l || tree[i].l > r)return 0;if(l <= tree[i].l && tree[i].r <= r)return tree[i].val;     pushDown(i);//如果要访问结点i的孩子,则标记下传return query(2*i, l, r)+query(2*i+1, l, r);
}

注意:本题数值范围达到 10 18 10^{18} 1018,与数值相关的变量都要设为long long类型。

解法2:树状数组

本解法不如使用线段树更加直观,建议读者掌握上述线段树解法。树状数组解法仅作为参考。
原数组为数组a。对a数组的差分数组d建树状数组。
如果对原数组进行区间修改,即对a序列区间 [ l , r ] [l, r] [l,r]中每个元素增加数值v,在差分数组d数组上的操作为 d l = d l + v , d r + 1 = d r + 1 − v d_l =d_l+ v, d_{r+1} =d_{r+1}-v dl=dl+v,dr+1=dr+1v
进行区间查询,求a序列 [ l , r ] [l, r] [l,r]中所有元素的和,可以借助a序列的前缀和求出。
a a a序列的前缀和为 s s s,则a序列区间 [ l , r ] [l, r] [l,r]中所有元素的和为 s r − s l − 1 s_r-s_{l-1} srsl1
已知 s i = ∑ x = 1 i a x s_i = \sum\limits_{x=1}^ia_x si=x=1iax a i = ∑ x = 1 i d x a_i = \sum\limits_{x=1}^id_x ai=x=1idx
所以
s i = ∑ x = 1 i ∑ y = 1 x d y s_i = \sum\limits_{x=1}^i\sum\limits_{y=1}^xd_y si=x=1iy=1xdy
= ∑ x = 1 i ( ∑ y = 1 i d y − ∑ y = x + 1 i d y ) + ( ∑ y = 1 i d y − ∑ y = 0 + 1 i d y ) = \sum\limits_{x=1}^i(\sum\limits_{y=1}^id_y-\sum\limits_{y=x+1}^id_y)+(\sum\limits_{y=1}^id_y-\sum\limits_{y=0+1}^id_y) =x=1i(y=1idyy=x+1idy)+(y=1idyy=0+1idy)
= ∑ x = 0 i ( ∑ y = 1 i d y − ∑ y = x + 1 i d y ) = \sum\limits_{x=0}^i(\sum\limits_{y=1}^id_y-\sum\limits_{y=x+1}^id_y) =x=0i(y=1idyy=x+1idy)
= ∑ x = 0 i ∑ y = 1 i d y − ∑ x = 0 i ∑ y = x + 1 i d y = \sum\limits_{x=0}^i\sum\limits_{y=1}^id_y-\sum\limits_{x=0}^i\sum\limits_{y=x+1}^id_y =x=0iy=1idyx=0iy=x+1idy
= ( i + 1 ) ∑ y = 1 i d y − ∑ x = 0 i ∑ y = x + 1 i d y = (i+1)\sum\limits_{y=1}^id_y-\sum\limits_{x=0}^i\sum\limits_{y=x+1}^id_y =(i+1)y=1idyx=0iy=x+1idy

∑ x = 0 i ∑ y = x + 1 i d y \sum\limits_{x=0}^i\sum\limits_{y=x+1}^id_y x=0iy=x+1idy
= ∑ y = 1 i d y + ∑ y = 2 i d y + . . . + ∑ y = i i d y =\sum\limits_{y=1}^id_y+\sum\limits_{y=2}^id_y+...+\sum\limits_{y=i}^id_y =y=1idy+y=2idy+...+y=iidy
= 1 d 1 + 2 d 2 + . . . + i d i = ∑ y = 1 i y ⋅ d y =1d_1+2d_2+...+id_i = \sum\limits_{y=1}^iy\cdot d_y =1d1+2d2+...+idi=y=1iydy

因此 s i = ( i + 1 ) ∑ y = 1 i d y − ∑ y = 1 i y ⋅ d y s_i = (i+1)\sum\limits_{y=1}^id_y-\sum\limits_{y=1}^iy\cdot d_y si=(i+1)y=1idyy=1iydy

假想存在f序列, f i = i ⋅ d i f_i = i\cdot d_i fi=idi
设两个树状数组 t 1 , t 2 t_1, t_2 t1,t2,分别维护 d d d序列和 f f f序列的区间和。
进行单点修改时,如果 d i = d i + v d_i=d_i+v di=di+v,那么 f i = i ⋅ ( d i + v ) = f i + i ⋅ v f_i=i\cdot(d_i+v)=f_i+i\cdot v fi=i(di+v)=fi+iv。使用树状数组单点修改操作完成上述修改,时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)
进行区间查询,求 s i s_i si,分别求出 d d d序列前i项的和 s d s_d sd,与 f f f序列前i项的和 s f s_f sf,而后 s i = ( i + 1 ) s d − s f s_i=(i+1)s_d-s_f si=(i+1)sdsf,时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

【题解代码】

解法1:线段树

#include<bits/stdc++.h>
using namespace std;
#define N 100005
struct Node
{int l, r;long long val, tag;
} tree[4*N];
long long n, m, a[N];
void pushUp(int i)
{tree[i].val = tree[2*i].val+tree[2*i+1].val;
}
void addTag(int i, long long v)//结点i增加标记v 
{tree[i].tag += v;tree[i].val += (tree[i].r-tree[i].l+1)*v; 
}
void pushDown(int i)//标记下传 
{if(tree[i].tag == 0)return;addTag(2*i, tree[i].tag);addTag(2*i+1, tree[i].tag);tree[i].tag = 0;
}
void build(int i, int l, int r)//建立线段树 
{tree[i].l = l, tree[i].r = r;if(l == r){tree[i].val = a[l];return;}int mid = l+r>>1;build(2*i, l, mid);build(2*i+1, mid+1, r);pushUp(i);
}
void update(int i, int l, int r, long long v)//区间修改 每个元素增加v 
{if(tree[i].r < l || tree[i].l > r)return;if(l <= tree[i].l && tree[i].r <= r)return addTag(i, v);pushDown(i);update(2*i, l, r, v);update(2*i+1, l, r, v);pushUp(i);
}
long long query(int i, int l, int r)//区间查询[l, r]中的值 
{if(tree[i].r < l || tree[i].l > r)return 0;if(l <= tree[i].l && tree[i].r <= r)return tree[i].val;pushDown(i);return query(2*i, l, r)+query(2*i+1, l, r);
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);long long op, x, y, k;cin >> n >> m;for(int i = 1; i <= n; ++i)cin >> a[i];build(1, 1, n);for(int i = 1; i <= m; ++i){cin >> op;if(op == 1){cin >> x >> y >> k;update(1, x, y, k);}else{cin >> x >> y;cout << query(1, x, y) << '\n'; }}return 0;
}

解法2:树状数组

#include<bits/stdc++.h>
using namespace std;
#define N 100005
long long n, m, t1[N], t2[N];//t1[i]:差分数组d的树状数组 t2[i]:i*d[i]的树状数组
int lowbit(int x)
{return x & -x;
}
void update(int i, long long  v)//对c1、c2的单点修改 
{for(int x = i; x <= n; x += lowbit(x))t1[x] += v, t2[x] += i*v;
}
void rangeUpdate(int l, int r, long long  v)//对a的区间修改为对d的两个单点修改 
{update(l, v);update(r+1, -v);
}
long long  sum(int n)//a的前n个元素的加和 
{long long sd = 0, sf = 0;for(int x = n; x > 0; x -= lowbit(x))sd += t1[x], sf += t2[x];return (n+1)*sd-sf;
}
long long query(int l, int r)
{return sum(r)-sum(l-1);
}
int main()
{long long a, op, x, y, k;cin >> n >> m;for(int i = 1; i <= n; ++i){cin >> a;rangeUpdate(i, i, a);}for(int i = 1; i <= m; ++i){cin >> op;if(op == 1){cin >> x >> y >> k;rangeUpdate(x, y, k);}else{cin >> x >> y;cout << query(x, y) << endl;}}return 0;
}

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

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

相关文章

软件更新 | TSMaster 202504 版本已上线!三大功能让车载测试更智能

车载测试的智能化时代正在加速到来&#xff01;TSMaster 202504 版本正式发布&#xff0c;本次更新聚焦以太网通信与数据高效处理&#xff0c;带来三大核心功能升级—以太网报文信息过滤、XCP on Ethernet支持、按时间范围离线回放&#xff0c;助力工程师更精准、更灵活地完成测…

java-单列集合list与set。

集合定位&#xff1a;存储数据的容器 与数组的区别&#xff1a; 数组只能存储同种数据类型数据&#xff0c;集合可以存储不同类型的数据。 数组的长度一旦创建长度不可变&#xff0c;集合的长度是可变的 数组的操作单一&#xff0c;集合的操作比较丰富&#xff08;增删改查&…

ai之pdf解析工具 PPStructure 还是PaddleOCR

目录 重点是四 先用 PPStructure 版面分析,分成不同的块儿,再选用 PaddleOCR、或PPStructure基础路径OCR模型配置OCR模型配置GPU配置硬件配置性能配置一、框架选型对比分析1. **PaddleOCR核心能力**2. **PP-Structure核心能力**3. **选型结论**二、错误根因分析与修复方案1. …

Android计算机网络学习总结

TCP vs UDP 核心区别​​ ​题目​&#xff1a;TCP为什么称为可靠传输协议&#xff1f;UDP在哪些场景下比TCP更具优势&#xff1f; ​得分要点​&#xff1a; ​可靠性机制​ 三握四挥建立可靠连接确认应答&#xff08;ACK&#xff09; 超时重传滑动窗口流量控制拥塞控制&…

深入解析Java组合模式:构建灵活树形结构的艺术

引言&#xff1a;当对象需要树形组织时 在日常开发中&#xff0c;我们经常需要处理具有层次结构的对象集合。比如&#xff1a; 文件系统中的文件夹与文件GUI界面中的容器与控件企业组织架构中的部门与员工 这类场景中的对象呈现明显的整体-部分层次结构&#xff0c;如何优雅…

mobaxterm通过ssh登录docker无图形界面

1. 流程 下面是使用Mobaxterm通过SSH登录Docker无图形界面的步骤&#xff1a; 步骤 操作 1 在本地安装Mobaxterm 2 配置Mobaxterm连接SSH 3 启动Docker容器 4 在Mobaxterm中通过SSH连接到Docker容器 2. 操作步骤 步骤1&#xff1a;安装Mobaxterm 首先&#xff…

【赵渝强老师】HBase的体系架构

HBase是大表&#xff08;BigTable&#xff09;思想的一个具体实现。它是一个列式存储的NoSQL数据库&#xff0c;适合执行数据的分析和处理。简单来说&#xff0c;就是适合执行查询操作。从体系架构的角度看&#xff0c;HBase是一种主从架构&#xff0c;包含&#xff1a;HBase H…

linux 新增驱动宏config.in配置

‌1. 添加配置宏步骤‌ ‌1.1 修改 Kconfig&#xff08;推荐方式&#xff09;‌ ‌定位 Kconfig 文件‌ 内核各子目录&#xff08;如 drivers/char/&#xff09;通常包含 Kconfig 文件&#xff0c;用于定义模块配置选项7。‌添加宏定义‌ 示例&#xff1a;在 drivers/char/Kc…

关于git的使用

下载git 可以去git的官网下载https://git-scm.com/downloads 也可以去找第三方的资源下载&#xff0c;下载后是一个exe应用程序&#xff0c;直接点开一直下一步就可以安装了 右键任意位置显示这两个就代表成功&#xff0c;第一个是git官方的图形化界面&#xff0c;第二个是用…

WPF【11_8】WPF实战-重构与美化(UI 与视图模型的联动,实现INotifyPropertyChanged)

11-13 【重构】INotifyPropertyChanged 与 ObservableCollection 现在我们来完成新建客户的功能。 当用户点击“客户添加”按钮以后系统会清空当前所选定的客户&#xff0c;客户的详细信息以及客户的预约记录会从 UI 中被清除。然后我们就可以在输入框中输入新的客户信息了&am…

ArkUI:鸿蒙应用响应式与组件化开发指南(一)

文章目录 引言1.ArkUI核心能力概览1.1状态驱动视图1.2组件化&#xff1a;构建可复用UI 2.状态管理&#xff1a;从单一组件到全局共享2.1 状态装饰器2.2 状态传递模式对比 引言 鸿蒙生态正催生应用开发的新范式。作为面向全场景的分布式操作系统&#xff0c;鸿蒙的北向应用开发…

List优雅分组

一、前言 最近小永哥发现&#xff0c;在开发过程中&#xff0c;经常会遇到需要对list进行分组&#xff0c;就是假如有一个RecordTest对象集合&#xff0c;RecordTest对象都有一个type的属性&#xff0c;需要将这个集合按type属性进行分组&#xff0c;转换为一个以type为key&…

AI与.NET技术实操系列(八):使用Catalyst进行自然语言处理

引言 自然语言处理&#xff08;Natural Language Processing, NLP&#xff09;是人工智能领域中最具活力和潜力的分支之一。从智能客服到机器翻译&#xff0c;再到语音识别&#xff0c;NLP技术正以其强大的功能改变着我们的生活方式和工作模式。 Catalyst的推出极大降低了NLP…

MySQL 8.0 OCP 1Z0-908 题目解析(13)

题目49 Choose the best answer. t is a non - empty InnoDB table. Examine these statements, which are executed in one session: BEGIN; SELECT * FROM t FOR UPDATE;Which is true? ○ A) mysqlcheck --analyze --all - databases will execute normally on all ta…

Docker 一键部署倒计时页面:Easy Countdown全设备通用

Easy Countdown 介绍 Easy countdown是一个易于设置的倒计时页面。可以设置为倒计时或计时器。可用于个人生活、工作管理、教育、活动策划等多个领域。 &#x1f6a2; 项目地址 Github&#xff1a;https://github.com/Yooooomi/easy-countdown &#x1f680;Easy Countdown …

Python训练打卡Day35

模型可视化与推理 知识点回顾&#xff1a; 三种不同的模型可视化方法&#xff1a;推荐torchinfo打印summary权重分布可视化进度条功能&#xff1a;手动和自动写法&#xff0c;让打印结果更加美观推理的写法&#xff1a;评估模式 模型结构可视化 理解一个深度学习网络最重要的2点…

四、生活常识

一、效应定律 效应 1、沉没成本效应 投入的越多&#xff0c;退出的难度就越大&#xff0c;因为不甘心自己之前的所有付出都付之东流。 2、破窗效应 干净的环境下&#xff0c;没有人会第一个丢垃圾&#xff0c;但是当环境变得糟糕&#xff0c;人们就开始无所妒忌的丢垃圾。…

机器学习圣经PRML作者Bishop20年后新作中文版出版!

机器学习圣经PRML作者Bishop20年后新书《深度学习&#xff1a;基础与概念》出版。作者克里斯托弗M. 毕晓普&#xff08;Christopher M. Bishop&#xff09;微软公司技术研究员、微软研究 院 科学智 能 中 心&#xff08;Microsoft Research AI4Science&#xff09;负责人。剑桥…

Python应用嵌套猜数字小游戏

大家好!今天向大家分享的是有关“嵌套”的猜数字小游戏。希望能够帮助大家理解嵌套。 代码呈现: # 1. 构建一个随机的数字变量 import random num random.randint(1, 10)guess_num int(input("输入你要猜测的数字&#xff1a; "))# 2. 通过if判断语句进行数字的猜…

黑马k8s(十四)

1.Service-概述 service&#xff1a;用于四层路由的负载&#xff0c;Ingress七层路由的负载&#xff1b;&#xff0c;先学习service 开启ipvs 2.Service-资源清单文件介绍 修改每个显示的内容 ClusterIP类型的Service Endpoints&#xff1a;建立service与pod关联 亲和性测试…