C++ 线段树

article/2025/9/30 1:18:04

目录:

线段树基本常识

线段树的的存储

线段树的基本操作

1. 建树

2. 单点查询

3. 单点修改

4. 区间查询

5. 区间修改

例题推荐:

线段树 1

线段树 2


线段树基本常识

        线段树是一种二叉搜索树(即每个节点最多有两颗子树),与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

        如下图,就是一颗 [1,10] 的线段树的分解过程:

        由图可得出一个结论,线段树的最大深度不超过 log_{2} n ,且每一颗线段树需要 4*n 的辅助空间,如果没有开到 4*n ,那就完美 RE 了!!!

线段树的的存储

        线段树采用的是堆式储存法,即编号为 k 的节点的做儿子为 k*2、右儿子为 k*2+1、父亲节点为k/2;二进制运算优化就是 k<<1,k<<1|1,k>>1。

        通常来说,每个节点会存储:区间左边界、区间右边界、答案及其有关信息。

线段树的基本操作

1. 建树

        采用递归遍历整棵树,每次将区间分为左右两个区间,对与叶子接点直接赋值,非叶子节点由左右子树合并。

inline  void build(int k,int l,int r)//k为当前节点,[l,r]为当前区间
{ if(l==r)//到达叶子节点,记录节点信息{sum[k]=a[l];return;//记得返回}int mid=(l+r)>>1;build(k*2,l,mid);//左右分别建树build(k*2+1,mid+1,r);sum[k]=sum[k*2]+sum[k*2+1];//合并区间信息return;
}

2. 单点查询

        采用类似二分的思想,将现区间 [ l,r ] 分为左右两个区间 [ l,mid ]、[ mid+1,r ] ,判断查找的位置 x 是在左区间还是在右区间,继续递归,如到达叶子节点,则直接返回节点信息。 

inline int find(int k,int l,int r,int x)//k为当前节点位置,[l,r]为当前区间,x为目标节点
{if(l==r) return sum[k];//找到目标节点,返回节点信息int mid=(l+r)>>1;if(x<=mid) return find(k*2,l,mid,x);//查询位置在左子树else  return find(k*2+1,mid+1,r,x);//查询位置在右子树
} 

3. 单点修改

        类似于单点查询,找到目标位置,直接进行修改,最后合并区间信息

inline void change(int k,int l,int r,int x)//k为当前节点位置,[l,r]为当前区间,x为目标节点
{if(l==r)//找到目标节点,进行修改{sum[k]+=x;//这里是区间累加return;}int mid=(l+r)>>1;if(x<=mid) change(k*2,l,mid,x);//所查询位置在左子树else change(k*2+1,mid+1,r,x);//所查询位置在右子树sum[k]=sum[k*2]+sum[k*2+1];//合并区间信息return;
}

4. 区间查询

        目标区间 [ x,y ] ,当前区间 [ l ,r ] ,如果当前区间被目标区间包含,则直接返回区间信息,否则按照将当前区间分为左右两个区间进行查找的思想继续递归查询,最后所有返回区间的并集就是目标区间的答案。

inline int find(int k,int l,int r,int x,int y)//k为当前节点位置,[l,r]为当前区间,[x,y]为目标区间
{if(x<=l&&r<=y) return sum[k];//当前区间被目标区间所包含,直接返回区间信息int mid=(l+r)>>1;int res=0;//用于左右子树区间值的累加if(x<=mid) res+=find(k*2,l,mid,x,y);//查询左子树if(y>mid) res+=find(k*2+1,mid+1,r,x,y);//查询右子树return res;//返回值
}

5. 区间修改

        如单点查询和单点修改一样,区间修改也是区间查询的一个延续,但不同的是,若修改区间内全部的节点信息,复杂度将会非常大,你也会迎来线段树的第一个 TLE ,于是,便有大佬按耐不住了,给我们引入了 “ 懒标记 ” 这一神奇函数。

        懒标记:若遍历到带有懒标记的节点,则立刻修改当前节点的值,并将标记下放到左右子树(这一过程被称为:标记下放),但左右子树的信息不发生改变,只有去遍历左右子树的时候,才修改其信息。

        懒标记大大提高了程序的运行效率,其核心思想就是:只修改对查询有用的节点。

inline void Add(int k,int l,int r,int v)//修改区间值
{add[k]+=v;//懒标记也要加vsum[k]+=v*(r-l+1);//区间值等于原值加上区间内数的个数乘上修改值return;
}
inline void pushdown(int k,int l,int r)//标记下方
{if(!add[k]) return;//判断是否带有懒标记int mid=(l+r)>>1;Add(k*2,l,mid,add[k]);//左子树修改同时下方标记Add(k*2+1,mid+1,r,add[k]);//右子树修改同时下方标记add[k]=0;//标记清空return;
}
inline int find(int k,int l,int r,int x,int y)//带懒标记的区间查询
{if(x<=l&&r<=y) return sum[k];int mid=(l+r)>>1;int res=0;pushdown(k,l,r);if(x<=mid) res+=find(k*2,l,mid,x,y);if(y>mid) res+=find(k*2+1,mid+1,r,x,y);return res;
}
inline void change(int k,int l,int r,int x,int y,int v)//k为当前节点位置,[l,r]为当前区间,[x,y]为目标区间
{if(x<=l&&r<=y)//当前区间被包含于目标区间{Add(k,l,r,v);//修改区间值return;}pushdown(k,l,r);//标记下方int mid=(l+r)>>1;if(x<=mid) change(k*2,l,mid,x,y,v);//左子树修改if(y>mid) change(k*2+1,mid+1,r,x,y,v);//右子树修改sum[k]=sum[k*2]+sum[k*2+1];//合并区间信息return;
}

奉上完整代码(无注释):

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<map>
#include<vector>
#include<string>
#include<cstring>
#include<queue>
using namespace std;
inline int read()
{int num=0,w=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();}while(ch>='0'&&ch<='9') {num=(num<<1)+(num<<3)+ch-'0';ch=getchar();}return num*w;
}
int n;
int a[4000001];
int sum[4000001];
int add[4000001];
inline  void build(int k,int l,int r)
{ if(l==r){sum[k]=a[l];return;}int mid=(l+r)>>1;build(k*2,l,mid);build(k*2+1,mid+1,r);sum[k]=sum[k*2]+sum[k*2+1];return;
}
inline int find_spot(int k,int l,int r,int x)
{if(l==r) return sum[k];int mid=(l+r)>>1;if(x<=mid) return find_spot(k*2,l,mid,x);else  return find_spot(k*2+1,mid+1,r,x);
} 
inline void change_spot(int k,int l,int r,int x)
{if(l==r) {sum[k]+=x;return;}int mid=(l+r)>>1;if(x<=mid) change_spot(k*2,l,mid,x);else change_spot(k*2+1,mid+1,r,x);sum[k]=sum[k*2]+sum[k*2+1];return;
}
inline void Add(int k,int l,int r,int v)
{add[k]+=v;sum[k]+=v*(r-l+1);return;
}
inline void pushdown(int k,int l,int r)
{if(!add[k]) return;int mid=(l+r)>>1;Add(k*2,l,mid,add[k]);Add(k*2+1,mid+1,r,add[k]);add[k]=0;return;
}
inline int find_section(int k,int l,int r,int x,int y)
{if(x<=l&&r<=y) return sum[k];int mid=(l+r)>>1;int res=0;pushdown(k,l,r);if(x<=mid) res+=find_section(k*2,l,mid,x,y);if(y>mid) res+=find_section(k*2+1,mid+1,r,x,y);return res;
}
inline void change_section(int k,int l,int r,int x,int y,int v)
{if(x<=l&&r<=y){Add(k,l,r,v);return;}pushdown(k,l,r);int mid=(l+r)>>1;if(x<=mid) change_section(k*2,l,mid,x,y,v);if(y>mid) change_section(k*2+1,mid+1,r,x,y,v);sum[k]=sum[k*2]+sum[k*2+1];return;
}
int main()
{//freopen(".in","r",stdin);//freopen(".out","w",stdout);//一些奇奇怪怪的初始化、读入、输出、离散化…… //fclose(stdin);//fclose(stdout);		return 0;
}

例题推荐:

P3372 【模板】线段树 1

P3373 【模板】线段树 2

线段树 1

        模板题,区间修改 + 区间查询 即可完美解决:

【代码实现】

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
int n,q,k,a,b,w,add[4000010],s[1000010];
long long sum[4000010];
void Add(int k,int l,int r,int v)
{add[k]+=v;sum[k]+=(long long)(r-l+1)*(long long)v; 
}
void down(int k,int l,int r,int mid)
{if(!add[k])return;Add(k*2,l,mid,add[k]);Add(k*2+1,mid+1,r,add[k]);add[k]=0;
}
void change(int k,int l,int r,int x,int y,int v)
{if(l>=x&&r<=y){Add(k,l,r,v);return;}int mid=(l+r)/2;down(k,l,r,mid);if(x<=mid){change(k*2,l,mid,x,y,v);}if(y>mid){change(k*2+1,mid+1,r,x,y,v);}sum[k]=sum[k*2]+sum[k*2+1];
}long long query(int k,int l,int r,int x,int y,int v)
{if(l>=x&&r<=y){return sum[k];}	int mid=(l+r)/2;long long res=0;down(k,l,r,mid);if(x<=mid){res+=query(k*2,l,mid,x,y,v);}if(y>mid){res+=query(k*2+1,mid+1,r,x,y,v);}return res;
}void build(int k,int l,int r)
{if(l==r){sum[k]=s[l];return;}int mid=(l+r)/2;build(k*2,l,mid);build(k*2+1,mid+1,r);sum[k]=sum[k*2]+sum[k*2+1];
}
int main()
{scanf("%d%d",&n,&q);for(int i=1;i<=n;i++){scanf("%d",&s[i]);}build(1,1,n);for(int i=1;i<=q;i++){scanf("%d",&k);if(k==1){scanf("%d%d%d",&a,&b,&w);change(1,1,n,a,b,w);}if(k==2){scanf("%d%d",&a,&b);printf("%lld\n",query(1,1,n,a,b,w));}}return 0;
}

线段树 2

        在 区间修改 + 区间查询的基础上,对懒标记进行亿点点的改动即可:

【代码实现】

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,p,m;
long long a[500001];
struct node
{long long sum;long long mul;//*long long pule;//+
}tree[500001];
int add(int k)
{tree[k].sum=tree[k*2].sum+tree[k*2+1].sum;tree[k].sum%=p;
}
void gs(int k,int l,int r)
{int k1=k*2;int k2=k*2+1;int mid=(l+r)/2;tree[k1].sum*=tree[k].mul;tree[k1].sum%=p;tree[k1].sum+=tree[k].pule*(mid-l+1);tree[k1].sum%=p;tree[k2].sum*=tree[k].mul;tree[k2].sum%=p;tree[k2].sum+=tree[k].pule*(r-mid);tree[k2].sum%=p;tree[k1].mul*=tree[k].mul;tree[k1].mul%=p;tree[k2].mul*=tree[k].mul;tree[k2].mul%=p;tree[k1].pule*=tree[k].mul;tree[k1].pule%=p;tree[k1].pule+=tree[k].pule;tree[k1].pule%=p;tree[k2].pule*=tree[k].mul;tree[k2].pule%=p;tree[k2].pule+=tree[k].pule;tree[k2].pule%=p;tree[k].pule=0;tree[k].mul=1;return;
}
void build(int k,int l,int r)
{tree[k].pule=0;tree[k].mul=1;if(l==r){tree[k].sum=a[l];return;}int mid=(l+r)/2;build(k*2,l,mid);build(k*2+1,mid+1,r);add(k);
}
void change_pule(int k,int l,int r,int x,int y,long long w)
{if(y<l||x>r){return;}if(x<=l&&r<=y){tree[k].pule+=w;tree[k].pule%=p;tree[k].sum+=w*(r-l+1);tree[k].sum%=p;return;}int mid=(l+r)/2;gs(k,l,r);change_pule(k*2,l,mid,x,y,w);change_pule(k*2+1,mid+1,r,x,y,w);add(k);
}
void change_mul(int k,int l,int r,int x,int y,long long w)
{if(y<l||x>r){return;}if(x<=l&&r<=y){tree[k].sum*=w;tree[k].sum%=p;tree[k].mul*=w;tree[k].mul%=p;tree[k].pule*=w;tree[k].pule%=p;return;}int mid=(l+r)/2;gs(k,l,r);change_mul(k*2,l,mid,x,y,w);change_mul(k*2+1,mid+1,r,x,y,w);add(k);
}
long long find(int k,int l,int r,int x,int y)
{if(x>r||y<l){return 0;}if(x<=l&&r<=y){return tree[k].sum;}int mid=(l+r)/2;gs(k,l,r);long long r1,r2;r1=find(k*2,l,mid,x,y);r2=find(k*2+1,mid+1,r,x,y);return (r1+r2)%p;
}
int main()
{scanf("%d%d%d",&n,&m,&p);for(int i=1;i<=n;i++){scanf("%lld",&a[i]);}build(1,1,n);for(int i=1;i<=m;i++){int k;scanf("%d",&k);if(k==1){int t,g;long long c;scanf("%d%d%lld",&t,&g,&c);change_mul(1,1,n,t,g,c);}if(k==2){int t,g;long long c;scanf("%d%d%lld",&t,&g,&c);change_pule(1,1,n,t,g,c);}if(k==3){int t,g;scanf("%d%d",&t,&g);printf("%lld\n",find(1,1,n,t,g));}}return 0;
}


http://chatgpt.dhexx.cn/article/eXjZDFVH.shtml

相关文章

线段树(java)

线段树描述&#xff1a; 线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数&#xff0c;时间复杂度为O(logN)。而未…

什么是线段树

线段树的概念 线段树是一种二叉搜索树&#xff0c;与区间树相似&#xff0c;它将一个区间划分成一些单元区间&#xff0c;每个单元区间对应线段树中的一个叶结点。 对于线段树中的每一个非叶子节点[a,b]&#xff0c;它的左儿子表示的区间为[a,(ab)/2]&#xff0c;右儿…

李超线段树

什么是李超线段树 先以一个问题引入&#xff1a; 在平面上有两种操作&#xff08;强制在线&#xff09;&#xff1a; 插入一条表达式为 L : y k*xb 的直线&#xff0c;给出 k ,b 。 给出 t&#xff0c;求当前所有直线中与直线 x t 交点的纵坐标最大是多少 直线取 max 应该是…

线段树详解 (原理,实现与应用)

线段树详解 By 岩之痕 目录&#xff1a; 一&#xff1a;综述 二&#xff1a;原理 三&#xff1a;递归实现 四&#xff1a;非递归原理 五&#xff1a;非递归实现 六&#xff1a;线段树解题模型 七&#xff1a;扫描线 八&#xff1a;可持久化 (主席树) 九&#xff1a;练习题 一&a…

线段树详解(附图解,模板及经典例题分析)

1.基本概念 ​ 线段树是一种常用来维护 区间信息 \color{Maroon}区间信息 区间信息 的数据结构 ​ 可以在 O(logn) 的时间复杂度内实现单点修改、区间修改、区间查询&#xff08;区间求和&#xff0c;求区间的最大值&#xff0c;求区间的最小值&#xff09;等操作 ​ 如下图…

线段树详解

文章目录 1. 什么是线段树2. 线段树的用处3. 线段树的模板题4. 动态开点线段树5. 权值线段树5. 可持久化线段树 1. 什么是线段树 顾名思义 &#xff0c; 线段树是一棵二叉树 &#xff0c; 但不同的是这棵树的结点储存的值是一个数列中 [ l , r ] [l,r] [l,r] 的某个需要的值 …

超详解线段树(浅显易懂,几乎涵盖所有线段树类型讲解,匠心之作,图文并茂)

一&#xff0c;什么是线段树&#xff1f; 线段树是怎样的树形结构? 线段树是一种二叉搜索树&#xff0c;而二叉搜索树&#xff0c;首先满足二叉树&#xff0c;即每个结点最多有两颗子树&#xff0c;并且是一颗搜索树&#xff0c;我们要知道&#xff0c;线段树的每个结点都存储…

线段树 从入门到进阶(超清晰,简单易懂)

目录 第一部 概念引入第二部 简单&#xff08;无pushdown&#xff09;的线段树1、单点修改&#xff0c;区间查询2、区间修改&#xff0c;单点查询 第三部 进阶线段树第四部 乘法&#xff08;根号&#xff09;线段树1、乘法线段树2、根号线段树模板题与代码&#xff1a;单点修改…

Navicat Premium 数据库开发工具

官网地址&#xff1a;https://www.navicat.com.cn/ Navicat Premium 是一套数据库开发工具&#xff0c;让你从单一应用程序中同时连接 MySQL、MariaDB、MongoDB、SQL Server、Oracle、PostgreSQL 和 SQLite 数据库。它与 Amazon RDS、Amazon Aurora、Amazon Redshift、Microso…

Navicat 一款数据库开发软件

Navicat premium是一款数据库管理工具&#xff0c;非常好用&#xff0c;能复制表结构等&#xff0c;大大简化了后端开发的工程量&#xff0c;其软件示意图如下 Navicat Premium 15 已经发布了&#xff0c;但是价格昂贵&#xff0c;所以将 no-money version 的双手奉上。 使用…

Java学习中的数据库和数据库开发工具

一、数据库 1、数据库&#xff0c;通常是一个戒一组文件&#xff0c;保存了一些符合特定规格的数据&#xff0c;数据库对应的英询单词是DataBase&#xff0c;简称DB&#xff1b;数据库软件称为数据库管理系统&#xff0c;英文简称DBMS&#xff0c;全称为DataBase Management S…

五大主流数据库深度对比!数据库开发、管理看这篇就够了

开发数据库应用&#xff0c;选择一个好的数据库是非常重要的。目前&#xff0c;商品化的数据库管理系统以关系型数据库为主导产品&#xff0c; 技术比较成熟。面向对象的数据库管理系统技术先进&#xff0c;数据库易于开发、维护。无论是关系型数据库还是非关系型数据库&#x…

mysql数据库开发环境_MySQL数据库教程-环境与集成开发工具

MySQL数据库基础教程 数据库是存储数据与管理数据的基础,也是目前绝大多数动态网站开发所必备的基础知识。MySQL数据库教程系列文章主要以MySQL数据库管理系统为例对关系型数据库相关定义与操作等进行讲解。并结合php与PDO等讲解,为初学者提供快速学习PHP动态网站开发所需的知…

C/C++ MySQL数据库开发

C/C++ MySQL数据库开发笔记 MySQL启动方式手动启动MySQL数据库:命令行方式启动MySQL数据库:配置环境变量MySQL在Linux下安装MySQL的配置文件重要参数设置C/C++ 访问MySQL数据库(MySQL开发环境的配置(MySQL开发头文件和库文件)MySQL 数据库的连接MySQL 数据类型以及对应的 …

1、 数据库开发规范

第1章 数据库开发规范的制定 数据库设计步骤&#xff1a; 1、数据结构设计&#xff1a;逻辑设计-》物理设计 2、实际工作中&#xff1a;逻辑设计物理设计 3、物理设计&#xff1a;表名字段名字段类型 数据库设计几个规范&#xff1a; 数据库命名规范、数据库基本设计规范…

数据库开发-2-开发数据库的要点

Lec2-开发数据库的要点 1. 开发成功数据库应用的特点 需要理解数据库体系结构需要理解锁和并发控制特性&#xff1a;每个数据库都以不同的方式实现最重要的是不要把数据库当"黑盒" 要了解每一个数据库的体系结构和特征最常见的问题就是因为我们对数据库本身了解不足…

数据库开发(Sqlite)

1、数据库开发 1.1 数据与数据管理 什么是信息&#xff1f;   信息是指对现实世界存在方式或运动状态的反应。 什么是数据&#xff1f;   数据是指存储在某一媒体上&#xff0c;能够被识别的物理符号&#xff1b;   数据的概念在数据处理领域已经被大为拓宽&#xff0c…

《软件测试的艺术》第4章:测试用例的设计-白盒测试

写在前面&#xff1a;原书中包含白盒测试、黑盒测试、错误猜测、测试策略四个小节&#xff0c;涵盖内容较多&#xff0c;因此按章节拆分叙述。 《软件测试的艺术》&#xff1a; 白盒测试--语句覆盖 语句覆盖的用例设计原则&#xff1a;将程序中的每条语句至少执行一次。 白盒…

《软件测试的艺术》读后感 Or 读书笔记

《软件测试的艺术》读后感 Or 读书笔记 第一章 一次自评价测试第二章 软件测试的心理学和经济学第三章 代码检查、走查与评审第四章 测试用例的设计第五章 模块&#xff08;单元&#xff09;测试第六章 更高级别的测试第七章 可用性&#xff08;或用户体验&#xff09;测试第八…

软件测试的艺术 学习笔记

文章目录 4.2黑盒测试4.2.1 等价划分4.2.2 边界值分析4.2.3 因果图 4.3 错误猜测4.4 测试策略 5. 模块(单元&#xff09;测试5.1 测试用例设计5.2 增量测试5.3 自顶向下测试与自底向上测试5.3.1 自顶向下的测试5.3.2 自底向上的测试5.3.3 比较 5.4 执行测试 6 更高级别的测试6.…