线段树及线段树模板

线段树详解 (原理,实现与应用) 线段树详解 By 岩之痕 一:综述 线段树是一种可以快速进行区间修改和区间查询的数据结构。点修改,区间修改和区间查询的复杂度都是O(log2(n)) 并且,线段树可以维护很多种类的信息。说到线段树就不得不提一下树状数组,树状数组维护的信息必须满足区间减法,因为树状数组只是维护1到X的信息,通过相减来得到区间信息,代码简单而且速度快,占用空间又少于线段树,所以一般情况下可以用树状数组的地方就 阅读详情

线段树

以下部分文本摘自百度。

1.简介

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN) 。而未优化的空间复杂度为 2 N 2N 2N ,实际应用时一般还要开空间为 4 N 4N 4N 的数组以免越界,因此有时需要离散化让空间压缩。

2.定义

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

对于线段树中的每一个非叶子节点 [ a , b ] [a,b] [a,b] ,它的左儿子表示的区间为 [ a , ( a + b ) / 2 ] [a,(a+b)/2] [a,(a+b)/2] ,右儿子表示的区间为 [ ( a + b ) / 2 + 1 , b ] [(a+b)/2+1,b] [(a+b)/2+1,b] 。因此线段树是平衡二叉树,最后的子节点数目为 N N N ,即整个线段区间的长度。

使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为 O ( log ⁡ N ) O(\log N) O(logN) 。而未优化的空间复杂度为 2 N 2N 2N ,实际应用时一般还要开空间为 4 N 4N 4N 的数组以免越界,因此有时需要离散化让空间压缩。

3.基本结构

线段树是建立在线段的基础上,每个结点都代表了一条线段 [ a , b ] [a,b] [a,b] 。长度为1的线段称为元线段。非元线段都有两个子结点,左结点代表的线段为 [ a , ( a + b ) / 2 ] [a,(a + b) / 2] [a,(a+b)/2],右结点代表的线段为 [ ( ( a + b ) / 2 ) + 1 , b ] [((a + b) / 2)+1,b] [((a+b)/2)+1,b] 。

长度范围为 [ 1 , L ] [1,L] [1,L] 的一棵线段树的深度为 log ⁡ L + 1 \log L + 1 logL+1 。这个显然,而且存储一棵线段树的空间复杂度为 O ( L ) O(L) O(L) 。

线段树支持最基本的操作为插入和删除一条线段。下面以插入为例,详细叙述,删除类似。

将一条线段 [ a , b ] [a,b] [a,b] 插入到代表线段 [ l , r ] [l,r] [l,r] 的结点 p p p 中,如果 p p p 不是元线段,那么令 m i d = ( l + r ) / 2 mid=(l+r)/2 mid=(l+r)/2 。如果 b < m i d b<mid b<mid ,那么将线段 [ a , b ] [a,b] [a,b] 也插入到 p p p 的左儿子结点中,如果 a > m i d a>mid a>mid ,那么将线段 [ a , b ] [a,b] [a,b] 也插入到 p p p 的右儿子结点中。

插入(删除)操作的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 。

4.实际应用

上面的都是些基本的线段树结构,但只有这些并不能做什么,就好比一个程序有输入没输出,根本没有任何用处。

最简单的应用就是记录线段是否被覆盖,随时查询当前被覆盖线段的总长度。那么此时可以在结点结构中加入一个变量 int count; ,代表当前结点代表的子树中被覆盖的线段长度和。这样就要在插入(删除)当中维护这个 count 值,于是当前的覆盖总值就是根节点的 count 值了。

另外也可以将 int count; 换成 bool cover; 支持查找一个结点或线段是否被覆盖。

实际上,通过在结点上记录不同的数据,线段树还可以完成很多不同的任务。例如,如果每次插入操作是在一条线段上每个位置均加 k,而查询操作是计算一条线段上的总和,那么在结点上需要记录的值为 sum。

这里会遇到一个问题:为了使所有sum值都保持正确,每一次插入操作可能要更新 O ( N ) O(N) O(N) 个 sum 值,从而使时间复杂度退化为 O ( N ) O(N) O(N) 。

解决方案是 Lazy 思想:对整个结点进行的操作,先在结点上做标记,而并非真正执行,直到根据查询操作的需要分成两部分。

根据 Lazy 思想,我们可以在不代表原线段的结点上增加一个值 toadd,即为对这个结点,留待以后执行的插入操作 k 值的总和。对整个结点插入时,只更新 sum 和 toadd 值而不向下进行,这样时间复杂度可证明为 O ( log ⁡ N ) O(\log N) O(logN) 。

对一个 toadd 值为 0 0 0 的结点整个进行查询时,直接返回存储在其中的 sum 值;而若对 toadd 不为 0 0 0 的一部分进行查询,则要更新其左右子结点的 sum 值,然后把 toadd 值传递下去,再对这个查询本身,左右子结点分别递归下去。时间复杂度也是 O ( N log ⁡ N ) O(N \log N) O(NlogN) 。

5.基本代码

P3372

(学习了皎月半洒花的代码)

#include <bits/stdc++.h>

#define int long long

#define us unsigned

using namespace std;

inline int read()
{
	int ww = 0,ee = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-')
		{
			ee = -1;
		}
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		ww = ww * 10 + ch - '0';
		ch = getchar();
	}
	return ww * ee;
}


us int n,m;

const int N = 4000010;
us int a[N];

us int ans[N],tag[N];

inline int ls(int a) //计算左儿子,同a*2 
{
	return (a << 1);
}
inline int rs(int a) //计算右儿子,同a*2+1 
{
	return (a << 1 | 1);
}

int op,opx,opy,opz;

void push_up(int x) //记录 x 节点的值 
{
	ans[x] = ans[ls(x)] + ans[rs(x)];
}

void build(int root,int l,int r)
{
	tag[root] = 0; //tag数组为懒标记
	if (l == r) //叶(子)节点,记录节点值,返回 
	{
		ans[root] = a[l];
		return ;
	}
	//不是叶(子)节点,继续往后搜 
	int mid = (l+r) / 2;
	build(ls(root),l,mid);
	build(rs(root),mid+1,r);
	push_up(root); //记录节点值 
}

void f(int x,int l,int r,int k)
{
	//记录当前节点所代表的区间
	ans[x] = ans[x] + k * (r-l+1);
	//记录节点值
	tag[x] = tag[x] + k;
	//懒标记 
}

void push_down(int x,int l,int r)
{
	int mid = (l+r) >> 1;
	f(ls(x),l,mid,tag[x]);
	f(rs(x),mid+1,r,tag[x]);
	tag[x] = 0;
	//每次更新两个子节点,不断向下传递,传递后清空 
}

void update(int xl,int xr,int l,int r,int x,int k)
{
	//xl,xp为要修改的区间
	//x为当前节点
	//l,r为x节点所存储的区间
	if (xl <= l && r <= xr)
	{
		tag[x] += k;
		ans[x] += k * (r-l+1);
		return ;
	}
	push_down(x,l,r);
	//向下传递
	int mid = (l+r) / 2;
	if (xl <= mid)
	{
		update(xl,xr,l,mid,ls(x),k);
	}
	if (xr > mid)
	{
		update(xl,xr,mid+1,r,rs(x),k);
	}
	push_up(x);
}

int query(int qx,int qy,int l,int r,int x)
{
	int ret = 0;
	if (qx <= l && r <= qy)
	{
		return ans[x];
	}
	int mid = (l+r) / 2;
	push_down(x,l,r);
	if (qx <= mid)
	{
		ret += query(qx,qy,l,mid,ls(x));
	}
	if (qy > mid)
	{
		ret += query(qx,qy,mid+1,r,rs(x));
	}
	return ret;
}

signed main()
{
	n = read();
	m = read();
	for (int i = 1;i <= n;i++)
	{
		a[i] = read();
	}
	build(1,1,n);
	//建立 根节点为1,区间为1-n的树 
	while (m--)
	{
		op = read();
		if (op == 1)
		{
			opx = read();
			opy = read();
			opz = read();
			update(opx,opy,1,n,1,opz);
		}
		else
		{
			opx = read();
			opy = read();
			printf("%lld\n",query(opx,opy,1,n,1));
		}
	}
	return 0;
}

无注释版

#include <bits/stdc++.h>

#define int long long

#define us unsigned

using namespace std;

inline int read()
{
	int ww = 0,ee = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9')
	{
		if (ch == '-')
		{
			ee = -1;
		}
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9')
	{
		ww = ww * 10 + ch - '0';
		ch = getchar();
	}
	return ww * ee;
}


us int n,m;

const int N = 4000010;
us int a[N];

us int ans[N],tag[N];

inline int ls(int a)
{
	return (a << 1);
}
inline int rs(int a)
{
	return (a << 1 | 1);
}

int op,opx,opy,opz;

void push_up(int x)
{
	ans[x] = ans[ls(x)] + ans[rs(x)];
}

void build(int root,int l,int r)
{
	tag[root] = 0;
	if (l == r)
	{
		ans[root] = a[l];
		return ;
	}
	int mid = (l+r) / 2;
	build(ls(root),l,mid);
	build(rs(root),mid+1,r);
	push_up(root);
}

void f(int x,int l,int r,int k)
{
	ans[x] = ans[x] + k * (r-l+1);
	tag[x] = tag[x] + k;
}

void push_down(int x,int l,int r)
{
	int mid = (l+r) >> 1;
	f(ls(x),l,mid,tag[x]);
	f(rs(x),mid+1,r,tag[x]);
	tag[x] = 0;
}

void update(int xl,int xr,int l,int r,int x,int k)
{
	if (xl <= l && r <= xr)
	{
		tag[x] += k;
		ans[x] += k * (r-l+1);
		return ;
	}
	push_down(x,l,r);
	int mid = (l+r) / 2;
	if (xl <= mid)
	{
		update(xl,xr,l,mid,ls(x),k);
	}
	if (xr > mid)
	{
		update(xl,xr,mid+1,r,rs(x),k);
	}
	push_up(x);
}

int query(int qx,int qy,int l,int r,int x)
{
	int ret = 0;
	if (qx <= l && r <= qy)
	{
		return ans[x];
	}
	int mid = (l+r) / 2;
	push_down(x,l,r);
	if (qx <= mid)
	{
		ret += query(qx,qy,l,mid,ls(x));
	}
	if (qy > mid)
	{
		ret += query(qx,qy,mid+1,r,rs(x));
	}
	return ret;
}

signed main()
{
	n = read();
	m = read();
	for (int i = 1;i <= n;i++)
	{
		a[i] = read();
	}
	build(1,1,n);
	while (m--)
	{
		op = read();
		if (op == 1)
		{
			opx = read();
			opy = read();
			opz = read();
			update(opx,opy,1,n,1,opz);
		}
		else
		{
			opx = read();
			opy = read();
			printf("%lld\n",query(opx,opy,1,n,1));
		}
	}
	return 0;
}
线段树详解 线段树详解、懒标记线段树、动态开点线段树 阅读详情

相关推荐

线段树--概念+模板+入门题

引子 子问题–1 给出n个数,m次问询,每次问询给出一个范围l、r,求区间l–r的总和 很容易想到用前缀和可以解决 O(n) 子问题–2 给出n个数,m次修改,每次给出一个范围l、r和一个值k,将区间l—r的数全部+k,m次修改后,再问询1次求区间l–r的总和 也可以想到这用差分就能解决 O(n) 最后将这两个问题结合起来,给出n个数,m次操作,操作有两种一个是将区间l–r的值+k,一个是询问区间l–r的总和。 这个问题与之前不同的地方是,之前想要求一个范围只需处理一次数据算前缀和,然后数据不变,可以一

Rikkaの博客 3694

【学习笔记】详解线段树(浅显易懂,匠心之作,图文并茂)

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

weq2011的博客 3万+

vivado生成及调用自定义IP的方法

开发环境:vivado 2018.2 1.将整个工程封装为自定义IP 将整个工程封装为自定义IP主要有以下步骤: 步骤1.选中工程中的top模块。点击Tools ------> Create and Package New IP,如图1所示 图1 步骤2.弹出界面后,点击next至图2界面,选择Package your current project,然后继续点击next 图2 步骤3.选择生成的IP路径,如图3所示。这个路径就是后续添加该IP的路径 图3 步骤4.继续点击next,然后选择fi

进击的阿日比 4730

算法数据结构——线段树(Segment Tree)精讲及python实现

线段树(Segment Tree):一种基于分治思想的二叉树,用于在区间上进行信息统计。它的每一个节点都对应一个区间leftright通常是整数。每一个叶子节点表示了一个单位区间(长度为1),叶子节点对应区间上。每一个非叶子节点的左子节点表示的区间都为,右子节点表示的的区间都为。线段树是一棵平衡二叉树,树上的每个节点维护一个区间。根节点维护的是整个区间,每个节点维护的是父亲节点的区间二等分之后的其中一个子区间。当有n。

白话机器学习 5171

Spyglass实战指南:跨时钟域同步方案深度解析

本文深度解析了Spyglass在跨时钟域同步方案验证中的实战应用。通过真实案例阐述了亚稳态的危害,并详细介绍了从基础的两级同步器到复杂的异步FIFO与握手协议等八大同步方案,以及如何利用Spyglass进行有效配置、检查与问题排查,为提升芯片设计可靠性提供系统性的避坑指南。

weixin_29219189的博客 837

基于51单片机的电压表【0-20V,ADC0832,4051,自动量程】(仿真)

自动换挡式数字电压表就是一个基于单片机的能自动切换量程的数字电压表,其作用是在使用低位的AD转换实现高精度电压测量,可以测量0~20V的电压,并且具有0~0.2V(待测电玉低于0.2V时),0~2V(待测电压低于2V时)和0~20V三个量程自动匹配测量,当待测电压值发生变化时,电压表可以根据输入电压情况自动选择合适的量程进行测量,并且把测量结果显示出来。(2)实现电压表精度测试,最高精度要求0.01V;(1)实现0~20V分3档式电压表;

qq_63964231的博客 1497

【模板】我自己的线段树

自用线段树模板,如果你觉得有用就学习学习吧~

A's World 287

Visual Studio Git 使用指南

Visual Studio 2022深度集成了Git功能,提供了直观的图形界面管理源代码。本文详细介绍了VS中Git的使用方法:从环境准备、克隆/创建仓库开始,重点讲解了"Git更改"窗口的日常操作,包括查看修改、暂存文件、提交推送等。同时涵盖了分支管理、查看历史、解决冲突等核心功能,以及储藏、重置等高级操作。文章还提供了实用技巧,如提交规范、频繁提交等最佳实践。VS的Git集成覆盖了日常开发需求,图形界面与命令行可互补使用,适合各层次开发者高效管理代码版本。

guigenyi的专栏 1568

线段树模板

线段树用于区间的修改统计问题,能用线段树维护的东西,必须满足区间可加性,即可用左右子结点推出父节点。 但其实线段树的用法远不止这些,这里来总结线段树的基本模板写法。 首先是递归建树 需要注意的地方是存树的数组要开4倍大小 push_up函数用来更新结点,这里以区间求和为例 inline void push_up(int k){ t[k]=t[k<<1]+t[k<<1|1]...

qq_43290092的博客 212

保姆级教程:在Windows 10/11上安装配置KNIME 5.8.0(含中文文本库更新)

本文提供在Windows 10/11系统上安装配置KNIME 5.8.0的详细指南,包括中文文本库更新和性能优化方案。从环境准备、安装流程到中文文本处理能力强化,逐步指导用户搭建高效的数据分析环境,特别针对国内网络环境和中文用户需求提供实用解决方案。

weixin_29081101的博客 670

matinal:SAP ABAP 关于sftp的发送接收源代码

如果您将目标系统的凭据存储在该 RFC 连接中,您可以安排程序并将其作为后台作业运行:凭据将从 SM59 配置中获取,并且在文件传输期间不会提示您输入它们。这尤其适用于安全问题:您有责任为您的编码添加安全功能,并相应地配置您的系统(例如,保护您的系统免受病毒侵害)。注意:在创建自己的程序时,请特别注意示例代码中以“@CUSTOMER”开头的注释,因为您需要实现自己的逻辑。本说明为您提供示例编码以支持您创建自己的程序。在 STRUST 事务中,您维护了一个 PSE 并导入了您要发送的文件的最终收件人的证书。

matinal 當冬夜漸暖 。公众号:matinal 1333

线段树 - 模板

线段树板子0: #include<cstdio> #include<iostream> #define ll long long #define _for(i,a,b) for(ll i = (a);i < (b); ++i) using namespace std; const ll maxn = 1e5+5; void build_tree(ll arr[],ll tree[],ll node,ll start,ll end){ //printf("Start : %

Spike的秃头之路 198

Vue - 列表拖曳排序 / 鼠标拖动改变顺序排列高效简洁组件(支持PC端与移动端触屏拖动,也可在滚动条内排序自动滚动,流畅丝滑无 BUG)

Vue-高效简洁列表拖曳排序/鼠标拖动改变顺序排列组件支持PC端与移动端触屏拖动,也可在滚动条内排序自动滚动,流畅丝滑无BUG列表拖曳排序插件vuedraggable拉动拽动拖动顺序改变排序js,vue.js,nuxt.js列表如何实现鼠标拖曳改变顺序排列支持滚动条内复杂数据对象带拖动效果动画排序完毕自动排列数组位置克隆Vue.Draggable官方文档教程SortableJS与vuedraggable列表拖动排序Vue拖拽组件库vuedraggable的安装与使用用户用鼠标拖动排序功能源码最好

🏆 前端领域优质创作者 4847

刘汝佳 线段树模板

一、线段树(点修改) update

远古小山的专栏 1万+

垂类大模型 研发方向与具体方案调研

领域知识库加上通用大模型,针对通用大模型见过的知识比较少的问题,利用向量数据库等方式根据问题在领域知识库中找到相关内容,再利用通用大模型强大的summarization和qa的能力生成回复,完成本次垂直大模型研发。目前很多企业希望将大模型的能力应用到企业内部当中,但很多通用大模型只是一个预训练模型,其所能回答的知识主要来源于互联网上公开的通用知识库,对于部分垂直领域和企业内部的私有知识库的问答,给出的回答。:垂类大模型的效果与底座性能息息相关,研发垂类大模型第一步就是确定开源大模型解决方案。

风吹落叶的博客 3329

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

线段树模板整理

综述线段树的原理:将[1,n]分解成若干特定的子区间(数量不超过4*n),然后,将每个区间[L,R]都分解为少量特定的子区间,通过对这些少量子区间的修改或者统计,来实现快速对[L,R]的修改或者统计。 作用:对编号连续的一些点的区间信息进行修改或者统计操作 主要操作:区间查询、点更新、区间更新 时间复杂度:修改和统计的复杂度都是O(log(N))由原理可以看出线段树维护的信息必须满足区间加法

歪歪T的拿金之路 8120

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

线段树模板1

#include<stdio.h> const int MAXN =2e5+5; typedef long long ll; //次模板一般用于大数据,开long long int numbers[MAXN]; struct node{ int l=0,r=0;//左 右结点 int val=0;//该点的值 }tree[MAXN*4]; // /* 模板大意:对n个数,...

AXKDD6555的博客 457

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

线段树 模板题 笔记

线段树 模板题 笔记

flyhigh521949的博客 648

线段树 模板之求和

本人水平有限,题解不到为处,请多多谅解 本蒟蒻谢谢大家观看 题目: Problem A: 线段树之Sum Time Limit:1 SecMemory Limit:128 MBSubmit:187Solved:130[Submit][Status][Web Board] Description 给定一数列,规定有两种操作,一是修改某个元素,二是...

167

线段树--模板

线段树的命名tree线段树的创建 build线段树的查询 query10月4日的线段树模板(现更新)问题:不够简洁#include&lt;iostream&gt; using namespace std; const int maxind=256; int segTree[maxind*4+10]; int array[maxind]; bool flag=false; inline min(in...

kala0的博客 355

【模板】线段树

【模板】线段树本篇博文只有模板,emmm。 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。而未优化的空间复杂度为2N,实际应用时一般还要开4N的数组以免越界,因此有时需要离散化让空间压缩。——摘自百度百科 因此,线段树是一种复杂度极为优秀(

Mashiro_ylb的博客 585

线段树模板(对应洛谷一道线段树模板题)

*最近正好在学线段树,搞懂后写一篇博客记录一下模板,供日后自己学习参考*

MrJinNEU的博客 437
下一篇: CSP考试为什么会爆零
AllenAC
博客等级 码龄3年 2113粉丝 239原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值