数据结构-浅谈线段树,树状数组[例题讲解学习]

news2024/10/5 15:26:39

🏆今日学习目标:
🍀学习算法-数据结构-线段树
✅创作者:贤鱼
⏰预计时间:30分钟
🎉个人主页:贤鱼的个人主页
🔥专栏系列:算法
🍁贤鱼的个人社区,欢迎你的加入 贤鱼摆烂团

在这里插入图片描述

数据结构

  • 🍁线段树
    • 🍀线段树的用途
    • 🍀线段树结构
    • 🍀建树
    • 🍀区间修改,单点修改
      • 🍎懒惰标记
      • 🍎加减
      • 🍎乘
    • 🍀区间查询
    • 🍀例题
      • 🍌【模板】线段树 2
        • 🍌题目描述
        • 🍌输入格式
        • 🍌输出格式
        • 🍌 样例 #1
            • 🍌样例输入 #1
          • 🍌样例输出 #1
        • 🍌提示
        • 🍌AC代码
  • 🍁树状数组
    • 🍀树状数组和线段树关系
    • 🍀用途
    • 🍀原理
      • 单点修改
      • 区间查询
    • 🍀例题
    • 🍀AC代码
  • 🍁结束语

🍁线段树

🍀线段树的用途

线段树可以实现单点修改,区间修改,区间查询等操作

为什么使用线段树

  • 可以在 O ( log ⁡ 2 n ) O(\log_2n) O(log2n)的时间复杂度内实现

🍀线段树结构

首先,线段树一定是一个二叉树

举个例子

a[6]={0,11,22,33,44,55};

这是一个数组,那么这个数组构建的线段树是什么样呢(黑色编号(下文tr介绍),红色数值)
在这里插入图片描述

设置a[1]为根节点,a[2]和a[3]分别是左右儿子
那么是不是可以理解为
a[n]的孩子是a[n*2]和a[n*2+1]
我们设一个数组tr[i]储存编号为i所包含的数值
我们将数字编号1-n取一个中间数mid
1~midmid+1~n分别分为左儿子右儿子(注意mid不要重复)

在这里插入图片描述

🍀建树

上面介绍了线段树的基本构成,下面详细介绍如何建树

创建build函数

	void build(int o,int l,int r){//o当前节点,l,r就是上文分的mid, l~mid 和 mid+1~r
		if(l==r){
			cin>>tr[o].a;
			return;
		}
		build(o<<1,l,M);//o<<1代表o*2,但是左移速度快很多
		build(o<<1|1,M+1,r);//|1代表+1,速度比+1快
		pushup(o);//下文介绍
	}

唠唠pushup

	void pushup(int o){
		tr[o].a=tr[o<<1].a+tr[o<<1|1].a;//很简单啦,tr[i]=tr[i/2]+tr[i/2+1]递归啦
	}

🍀区间修改,单点修改

🍎懒惰标记

什么是懒惰标记(lazy)呢?

  • 用来储存当前节点的状态(只有修改数值的时候会用到)

举个栗子(将2-3的每一个值增加3),我们就会将tr[2]和tr[3]的父亲的lazy记为3,这样子,如果tr[i].lazy有值,我们就往下推一位,将他的两个儿子lazy和值分别+3,然后清空当前lazy

注意,如果是往下推的lazy,必须累加,避免顶替之前的状态

为什么只往下推一位?
反正我记录lazy了,用到它了再推,可以节省时间麻~,反正多次推到同一个位置也是累加,不需要每次推到底(乘法另讲)


上文创建的tr,需要用结构体

	struct node{
		int a;
		int laz;//懒惰标记
	}tr[400040];

🍎加减

	void update(int o,int l,int r,int ql,int qr,int k){//o当前节点,lr是当前范围. ql,qr是修改范围,k是修改值
		if(ql<=l&&qr>=r){//包含就修改当前值并且记录lazy
			tr[o].a+=(r-l+1)*k;
			tr[o].laz+=k;
			return;
		}
		if(tr[o].laz)down(o,l,r);//这里down就是往下推,下文会有
		if(ql<=M) update(o<<1,l,M,ql,qr,k);
		if(qr>M) update(o<<1|1,M+1,r,ql,qr,k);//寻找合适范围,像不像二分~,原理如下图(千万不要if else!!!!!!!!!,可能存在两种都有的情况)
		pushup(o);

	}

假设修改2-4的值
在这里插入图片描述
符合ql<=mid,不符合qr>mid

在这里插入图片描述
两个都符合

在这里插入图片描述
灰色lr区间找到

在这里插入图片描述
全部找到

	void down(int o,int l,int r){//按照父亲节点的lazy修改当前值
		tr[o<<1].a+=(M-l+1)*tr[o].laz;
		tr[o<<1|1].a+=(r-M)*tr[o].laz;
		tr[o<<1].laz+=tr[o].laz;
		tr[o<<1|1].laz+=tr[o].laz;
		tr[o].laz=0;//记得清零
	}

🍎乘

和+有亿点点区别,需要记录lazx,
tr[i]的lazx往下推的时候,需要用儿子的lazx*父亲的lazx
我乘一个数字,然后上面右往下乘了一个数字,是不是要互相乘
tr[i]的laz往下推的时候,需要用儿子的laz*父亲的lazx+父亲的laz
我加一个数字,是不是要先乘上面的数字(没有乘的时候lazx=1)再加上面推的数字
比较绕,但是读几遍应该可以理解

修改也是同理
当前的laz要乘修改的k
当前的lazx也要乘修改的k
乘法,那么未往下推的laz是不是也要相对应的乘

	void down(int x,int l,int r){
		if(tr[x].lazx==1&&tr[x].laz==0) return;
		tr[x<<1].lazx=tr[x].lazx*tr[x<<1].lazx;
		tr[x<<1|1].lazx=tr[x].lazx*tr[x<<1|1].lazx;
		tr[x<<1].laz=tr[x<<1].laz*tr[x].lazx+tr[x].laz;
		tr[x<<1|1].laz=tr[x<<1|1].laz*tr[x].lazx+tr[x].laz;
		tr[x<<1].a=tr[x<<1].a*tr[x].lazx+(M-l+1)*tr[x].laz;
		tr[x<<1|1].a=tr[x<<1|1].a*tr[x].lazx+(r-M)*tr[x].laz;
		tr[x].laz=0;
		tr[x].lazx=1;
	}
		void update1(int o,int l,int r,int ql,int qr,int k){
		if(ql<=l&&qr>=r){
			tr[o].a=tr[o].a*k;
			tr[o].laz=tr[o].laz*k;
			tr[o].lazx=tr[o].lazx*k;
			return;
		}
		down(o,l,r);
		if(ql<=M) update1(o<<1,l,M,ql,qr,k);
		if(qr>M) update1(o<<1|1,M+1,r,ql,qr,k);
		up(o);
	}

🍀区间查询

看懂了上面的,这个其实和他差不多.符合就返回值,不然继续寻找区间

	int query(int o,int l,int r,int ql,int qr){
		int ans=0;
		if(ql<=l&&qr>=r){
			return tr[o].a;
		}
		if(tr[o].laz)down(o,l,r);
		if(ql<=M) ans+=query(o<<1,l,M,ql,qr);
		if(qr>M) ans+=query(o<<1|1,M+1,r,ql,qr);
		return ans;
	}

🍀例题

🍌【模板】线段树 2

🍌题目描述

如题,已知一个数列,你需要进行下面三种操作:

  • 将某区间每一个数乘上 x x x
  • 将某区间每一个数加上 x x x
  • 求出某区间每一个数的和。
🍌输入格式

第一行包含三个整数 n , q , m n,q,m n,q,m,分别表示该数列数字的个数、操作的总个数和模数。

第二行包含 n n n 个用空格分隔的整数,其中第 i i i 个数字表示数列第 i i i 项的初始值。

接下来 q q q 行每行包含若干个整数,表示一个操作,具体如下:

操作 1 1 1: 格式:1 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数乘上 k k k

操作 2 2 2: 格式:2 x y k 含义:将区间 [ x , y ] [x,y] [x,y] 内每个数加上 k k k

操作 3 3 3: 格式:3 x y 含义:输出区间 [ x , y ] [x,y] [x,y] 内每个数的和对 m m m 取模所得的结果

🍌输出格式

输出包含若干行整数,即为所有操作 3 3 3 的结果。

🍌 样例 #1
🍌样例输入 #1
5 5 38
1 5 4 2 3
2 1 4 1
3 2 5
1 2 4 2
2 3 5 5
3 1 4
🍌样例输出 #1
17
2
🍌提示

【数据范围】

对于 30 % 30\% 30% 的数据: n ≤ 8 n \le 8 n8 q ≤ 10 q \le 10 q10
对于 70 % 70\% 70% 的数据:$n \le 10^3 , , q \le 10^4$。
对于 100 % 100\% 100% 的数据: 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105 1 ≤ q ≤ 1 0 5 1 \le q \le 10^5 1q105

除样例外, m = 571373 m = 571373 m=571373

(数据已经过加强 _

样例说明:

故输出应为 17 17 17 2 2 2 40   m o d   38 = 2 40 \bmod 38 = 2 40mod38=2)。

🍌AC代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
#define int long long
#define M ((l+r)/2)%m
int n,m,q;
namespace tr{
	struct node{
		int a;
		int laz;
		int lazx;
	}tr[4004000];
	void up(int x){
		tr[x].a=(tr[x<<1].a%m+tr[x<<1|1].a%m)%m;
	}
	
	void build(int o,int l,int r){
		tr[o].lazx=1;
		if(l==r){
			cin>>tr[o].a;
			return;
		}
		build(o<<1,l,M);
		build(o<<1|1,M+1,r);
		up(o);
	}
	
	void down(int x,int l,int r){//就是增加了一~大~堆~mod防止爆int
		if(tr[x].lazx==1&&tr[x].laz==0) return;
		tr[x<<1].lazx=1ll*(tr[x].lazx%m*tr[x<<1].lazx%m)%m;
		tr[x<<1|1].lazx=1ll*(tr[x].lazx%m*tr[x<<1|1].lazx%m)%m;
		tr[x<<1].laz=(1ll*(tr[x<<1].laz%m*tr[x].lazx%m)%m+tr[x].laz%m)%m;
		tr[x<<1|1].laz=(1ll*(tr[x<<1|1].laz%m*tr[x].lazx%m)%m+tr[x].laz%m)%m;
		tr[x<<1].a=(1ll*(tr[x<<1].a%m*tr[x].lazx%m)%m+(M-l+1)*tr[x].laz%m)%m;
		tr[x<<1|1].a=(1ll*(tr[x<<1|1].a%m*tr[x].lazx%m)%m+(r-M)*tr[x].laz%m)%m;
		tr[x].laz=0;
		tr[x].lazx=1;
	}
	
	void update(int o,int l,int r,int ql,int qr,int k){
		if(ql<=l&&qr>=r){
			tr[o].a+=(1ll*(r-l+1)*k%m)%m;
			tr[o].laz+=k%m;
			return ;
		}
		down(o,l,r);
		if(ql<=M) update(o<<1,l,M,ql,qr,k);
		if(qr>M) update(o<<1|1,M+1,r,ql,qr,k);
		up(o);
	}
	
	void update1(int o,int l,int r,int ql,int qr,int k){
		if(ql<=l&&qr>=r){
			tr[o].a=(tr[o].a%m*k%m)%m;
			tr[o].laz=(tr[o].laz%m*k%m)%m;
			tr[o].lazx=(tr[o].lazx%m*k%m)%m;
			return;
		}
		down(o,l,r);
		if(ql<=M) update1(o<<1,l,M,ql,qr,k);
		if(qr>M) update1(o<<1|1,M+1,r,ql,qr,k);
		up(o);
	}
	int query(int o,int l,int r,int ql,int qr){
		int ans=0;
		if(ql<=l&&qr>=r){
			return tr[o].a;
		}
		down(o,l,r);
		if(ql<=M) ans=(ans%m+query(o<<1,l,M,ql,qr)%m)%m;
		if(qr>M) ans=(ans%m+query(o<<1|1,M+1,r,ql,qr)%m)%m;
		return ans%m;
	}
}
using namespace tr;
signed main(){
	cin>>n>>q>>m;
	build(1,1,n);
	while(q!=0){
		q--;
		int w;
		cin>>w;
		int x,y,kk;
		if(w==1){
			cin>>x>>y>>kk;
			update1(1,1,n,x,y,kk);
		}else if(w==2){
			cin>>x>>y>>kk;
			update(1,1,n,x,y,kk);
		}else{
			cin>>x>>y;
			cout<<query(1,1,n,x,y)%m<<endl;
		}
	}
}

🍁树状数组

🍀树状数组和线段树关系

树状数组可以做的,线段树一定可以做,反之则不一定

🍀用途

树状数组可以支持单点修改和区间查询

🍀原理

如图是一个树状数组,a[i]代表当前包含内容的和
在这里插入图片描述
如何得到这个的呢? 看二进制

十进制二进制
11
210
311
4100

所以,从右往左,第一个1在哪里,当前就包含多少个内容(只算一层,不是算到底(例如4包含2,3,4,不算2,3包含的内容))

单点修改

首先我们需要知道一个操作lowbit,很简单,寻找二进制下右往左第一个1的

int lowbit(int x){
	return x&-x;
}

单点修改,同时输入也是这个

void add(int x, int k){
  while(x<=n){  
    a[x]=a[x]+k;
    x=x+lowbit(x);//一次性修改所有,从下一直修改到顶
  }
}

如上图如果1修改了,那么2,4,8的值都会改变

区间查询

int getsum(int x) { //和上面差不多,只不过这里求出来的和是1-n的,如果求l-r,需要getsum(r)-getsum(l-1)
  int ans=0;
  while(x>0){
    ans=ans+a[x];
    x=x-lowbit(x);
  }
  return ans;
}

例如求a[5]-a[7]的和
在这里插入图片描述
红色减去蓝色剩下的就是求的内容了

🍀例题

在这里插入图片描述

🍀AC代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#define int long long
using namespace std;
int n,m;
int a[10000005];
int l,r,k;
int lowbit(int x){
	return x&-x;
}
int getsum(int x) { 
  int ans=0;
  while(x>0){
    ans=ans+a[x];
    x=x-lowbit(x);
  }
  return ans;
}

void add(int x, int kk){
  while(x<=n){  
    a[x]=a[x]+kk;
    x=x+lowbit(x);
  }
}

signed main(){
	cin>>n>>m;
	int w;
	for(int i=1;i<=n;i++)
		cin>>w,add(i,w);
	while(m!=0){
		m--;
		int x;
		cin>>x;
		if(x==1){
			cin>>l>>k;
			add(l,k);
		}else{
			cin>>l>>r;
			cout<<getsum(r)-getsum(l-1)<<endl;
		}
	}
}

🍁结束语

如果对您有帮助的话,点个赞支持一下贤鱼吧🏆

请添加图片描述

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

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

相关文章

VScode 设置终端窗口 terminal 的字体大小

1. 打开vscode的设置 2. 找到terminal&#xff0c;不同vscode版本&#xff0c;可能会有差异&#xff0c;找terminal 就行&#xff0c;修改字体后&#xff0c;等1s延迟&#xff0c;字体会自动变大或变小&#xff0c;我设的是18

进程等待..

进程等待: 1.是什么? 通过系统调用wait/waitpid&#xff0c;来进行对子进程进行状态检测与回收的功能! 2.为什么? 僵尸进程无法被杀死&#xff0c;需要通过进程等待来杀掉它&#xff0c;进而解决内存泄漏问题—必须解决的 我们要通过进程等待&#xff0c;获得子进程的退出情…

JVM常见的垃圾回收器(详细)

1、Young为年轻代出发的垃圾回收器。 2、Old为老触发的垃圾回收器。 3、连线代表的是垃圾回收器的组合。CMS 和Serial Old连线代表CMS一旦不行了&#xff0c;Serial Old上场。 首先了解一个概念&#xff1a;STW 1、什么是STW&#xff1f; STW是Stop-The-World缩写: 是在垃圾回…

Vue响应式数据的实现原理(手写副作用函数的存储和执行过程)

1.命令式和声明式框架 命令式框架关注过程 声明式框架关注结果&#xff08;底层对命令式的DOM获取和修改进行了封装&#xff09; 2.vue2 Object.defineProperty()双向绑定的实现 <body><div id"app"><input type"text" /><h1>…

Ni-IDA琼脂糖凝胶FF-------可用于纯化带组氨酸标签(His-Tag)的重组蛋白

品 名&#xff1a;Ni-IDA琼脂糖凝胶FF(Nickel Iminodiacetic acid Pharose Fast Flow, Ni-IDA Pharose FF) 规 格&#xff1a;10 ml&#xff0c;100 ml&#xff0c;1L&#xff0c;1 ml预装柱&#xff0c;5 ml预装柱 贮 存&#xff1a;20%乙醇&#xff0c;2-25℃ 运…

国产信号发生器 1442/1442A射频信号发生器

信号发生器 1442/A射频信号发生器 1442系列射频信号发生器是一款针对通信、电子等射频应用而设计开发的产品。覆盖了所有的常用射频频段。它采用模块化结构设计&#xff0c;全中文界面、大屏幕菜单控制&#xff0c;其输出信号相位噪声极低&#xff0c;频率分辨率和准确度高&am…

遥感语义分割、变化检测论文小trick合集(持续更新)

目录 &#x1f497;&#x1f497;1.影像融合机制 &#x1f497;&#x1f497;2.上下文聚合模块 &#x1f497;&#x1f497;3.adapter即插即用模块 &#x1f497;&#x1f497;1.影像融合机制 参考【多源特征自适应融合网络的高分遥感影像语义分割】文章中的“多源特征自适应…

注意力机制QKV在GAT(Graph Attention Network)的体现

注意力机制其实并没有规定 Q、K、V 的具体来源&#xff0c;GAT是规定了一套Q、K、V&#xff0c;自注意力是规定了另一套Q、K、V。核心其实只要计算满足下图的矩阵形式计算流程就是所谓的注意力机制了。学过注意力机制的应该都看的明白。 在自注意力机制self-attention中&…

竞赛选题 深度学习卷积神经网络的花卉识别

文章目录 0 前言1 项目背景2 花卉识别的基本原理3 算法实现3.1 预处理3.2 特征提取和选择3.3 分类器设计和决策3.4 卷积神经网络基本原理 4 算法实现4.1 花卉图像数据4.2 模块组成 5 项目执行结果6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基…

IP地址:网络层的介绍

我们花费很大的篇幅讲解了应用层的TCP和UDP协议。现在我们进入到网络层的学习&#xff0c;重点要学习的就是IP协议。 对于IP协议来说&#xff0c;重要的有IPv4和IPv6协议&#xff0c;我们重点介绍IPv4协议。 IP报头 4位版本&#xff1a; 此处的取值只有4和6&#xff0c;代表…

Redis安全之从入门到花式利用

0x00 安全研究思路 正常安全研究思路大致可以是这样&#xff1a; 正常功能&#xff0c;为什么这个功能会导致漏洞&#xff0c;怎么使用不会有漏洞&#xff0c;开发为什么会这么写如何攻击&#xff0c;攻击会遇到什么情况什么限制如何解决如何武器化如何防御&#xff0c;在什么…

uniapp开发小程序—picker结合后台数据实现二级联动的选择

一、效果图 二、完整代码 <template><view><picker mode"multiSelector" change"bindMultiPickerChange" columnchange"bindMultiPickerColumnChange":value"multiIndex" :range"multiArray"><view c…

做外贸为何离不开WhatsApp?一文解封、养号、引流、促单全攻略!

WhatsApp在国际贸易中的地位无法忽视。它是一种即时通讯工具&#xff0c;也是外贸从业者的得力助手。但同时&#xff0c;使用WhatsApp也伴随着一些问题&#xff0c;如账号被封、如何养号、引流和促单。这篇文章将为你详细解答这些问题&#xff0c;让你更好地利用WhatsApp&#…

个人企业项目招投标小程序开发

项目招投标小程序开发 针对个人企业招投标开发的小程序。 程序基本能力&#xff1a;用户缴纳保证发布招标信息&#xff0c;然后商家进行认证成功后可以对招标发起投标&#xff0c;投标过程也需要缴纳保证金&#xff0c;招标结束或者下架保证金将全部退回到用户账号里面。 招…

生物芯片技术-原理、应用与未来发展

生物芯片技术-原理、应用与未来发展 一、引言 随着科技的不断发展&#xff0c;生物芯片技术已成为生物医药领域的重要支柱。这种技术运用微电子和微机械工艺&#xff0c;将生物分子、细胞、组织等生命活性物质固定在硅片、玻璃片、塑料片等固相基质上&#xff0c;实现生物信息…

filebeat7.10上传日志到ES7.14

filebeat版本&#xff1a;filebeat-7.10.0 版本&#xff1a;filebeat-7.10.0-linux-x86_64.tar.gz filebeat7.10上传日志到ES7.14 1、下载filebeat wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.10.0-linux-x86_64.tar.gz 2、安装filebeat ta…

从果蔬到乳制品,探索食品微生物对肠道健康的影响

谷禾健康 俗话说病从口入&#xff0c;饮食对人体具有重要的影响&#xff0c;蔬菜和水果作为每日饮食中必不可少的成分&#xff0c;为人类提供了重要的营养物质&#xff0c;包括各种必需的维生素和矿物质。 此外&#xff0c;蔬菜和水果上栖息着数量惊人的微生物&#xff0c;高度…

ASO优化之通过页面的优化来提升排名

应用商店优化是一个持续优化应用列表的过程&#xff0c;从而让我们的应用更容易被目标受众发现。通过实施ASO&#xff0c;我们可以在竞争激烈的应用市场中有效竞争&#xff0c;并为我们的应用带来自然流量。 1、添加关键词。 进行关键词研究&#xff0c;从而确定与应用程序功能…

【JavaSE语法】数据类型与变量

一、字面常量 常量即程序运行期间&#xff0c;固定不变,不可修改的量称为常量 public class Demo {public static void main(String[] args) {System.out.println("hello World!");System.out.println(100);System.out.println(3.14);System.out.println(A);System…

【欧拉函数】CF1731E

Problem - E - Codeforces 题意 思路 对于 k 次操作&#xff0c;gcd(u, v) k 1&#xff0c;代价的贡献就是二元组 (u, v)的个数 * (k 1) 那么就要我们求二元组个数 这个是个很经典的欧拉函数的套路&#xff0c;可以用线性筛把欧拉函数求出来&#xff0c;然后求个前缀和 …