首页 > 其他分享 >线段树

线段树

时间:2023-08-11 21:45:42浏览次数:36  
标签:int 线段 long add sum 区间 节点

线段树

线段树是一种二叉树形数据结构,用于解决区间查询和区间修改问题。它将一个数组划分为若干个连续的区间,每个区间对应线段树的一个节点。通过递归地构建线段树,我们可以在O(log n)的时间复杂度内完成区间查询和区间修改操作。

原理

线段树的构建过程如下:

  1. 将原数组划分为n个子区间,左闭右开,例如对于数组[1, 2, 3, 4, 5],划分为[1, 3], [2, 4], [3, 5]三个子区间。
  2. 递归地构建左右子树。
  3. 将根节点的信息设置为区间的最大值、最小值、最大值索引、最小值索引等。

线段树的查询和修改过程如下:

  1. 查询操作:给定一个区间[l, r],在根节点中查找该区间的最大值和最小值。如果l <= 区间起始位置 <= r,则更新根节点的信息;否则递归地在左右子树中查找。
  2. 修改操作:给定一个区间[l, r],在根节点中查找该区间的最大值和最小值。如果l <= 区间起始位置 <= r,则更新根节点的信息;否则递归地在左右子树中查找。然后根据需要进行合并操作,合并后的区间范围是[l', r'] = [l, r],其中l' = min(l, l'),r' = max(r, r')。

C++代码实现

#include <iostream>
#include <cstdio>
#define MAXN 100001
using namespace std;
int n,m,a[4*MAXN],tot;
long long ans[MAXN];
struct SegmentTree{
	int L,R;
	long long sum,add;
#define L(x) tree[x].L
#define R(x) tree[x].R
#define sum(x) tree[x].sum
#define add(x) tree[x].add
}tree[4*MAXN];
// 定义了一个线段树节点的结构体
struct SegmentTree{
	int L,R; // 当前节点所代表的区间范围,L为左端点,R为右端点
	long long sum,add; // sum表示以当前节点为根节点的区间和,add表示要添加到当前节点的值
#define L(x) tree[x].L // 宏定义,获取当前节点的左端点
#define R(x) tree[x].R // 宏定义,获取当前节点的右端点
#define sum(x) tree[x].sum // 宏定义,获取当前节点的和
#define add(x) tree[x].add // 宏定义,获取当前节点要添加的值
}tree[4*MAXN]; // 初始化一个线段树数组,每个元素都是一个SegmentTree结构体

// 构建线段树的函数
void build(int p, int l, int r){
	L(p)=l,R(p)=r; // 将当前节点的范围设置为输入的范围
	if(l==r){ // 如果只有一个元素,那么这个元素就是整个区间的和
		sum(p)=a[l]; // 并将这个元素的值赋给当前节点的和
		return ;
	}
	int mid=(l+r)>>1; // 计算中间元素的位置
	build(p*2,l,mid); // 递归构建左子树
	build(p*2+1,mid+1,r); // 递归构建右子树
	sum(p)=sum(p*2)+sum(p*2+1); // 将左右子树的和相加得到当前节点的和
	return ;
}

// 将新增的值分发到所有相关的节点上
void spread(int p){
	if(add(p)){ // 如果有新的值要添加到当前节点
		sum(p*2)+=add(p)*(R(p*2)-L(p*2)+1); // 将新值添加到左子树中
		sum(p*2+1)+=add(p)*(R(p*2+1)-L(p*2+1)+1); // 将新值添加到右子树中
		add(p*2)+=add(p); // 将新值添加到当前节点的左子树中
		add(p*2+1)+=add(p); // 将新值添加到当前节点的右子树中
		add(p)=0; // 将当前节点的add值重置为0
	}
	return ;
}

// 在指定区间内修改原有的值并更新相应的节点
void change(int p, int l, int r, int d){
	if(l<=L(p)&&r>=R(p)){ // 如果指定的区间完全包含在当前节点内
		sum(p)+=(long long)d*(R(p)-L(p)+1); // 将指定区间内的值加上d并更新当前节点的和
		add(p)+=d; // 将新增的值添加到当前节点中
		return ;
	}
	spread(p); // 如果指定的区间不完全包含在当前节点内,先将新增的值分发到所有相关的节点上
	int mid=(L(p)+R(p))>>1; // 计算中间位置
	if(l<=mid) change(p*2,l,r,d); // 如果指定区间的一部分在左子树中,递归调用change函数处理左子树
	if(r>mid) change(p*2+1,l,r,d); // 如果指定区间的一部分在右子树中,递归调用change函数处理右子树
	sum(p)=sum(p*2)+sum(p*2+1); // 将左右子树的和相加得到当前节点的和
	return;
}
// 查询指定区间内的和
long long ask(int p, int l, int r){
	if(l<=L(p)&&r>=R(p)) return sum(p); // 如果指定区间完全包含在当前节点内,直接返回当前节点的和
	spread(p); // 先将新增的值分发到所有相关的节点上
	int mid=(L(p)+R(p))>>1; // 计算中间位置
	long long val=0; // 初始化结果变量为0
	if(l<=mid) val+=ask(p*2,l,r); // 如果指定区间的一部分在左子树中,递归调用ask函数处理左子树并将结果相加
	if(r>mid) val+=ask(p*2+1,l,r); // 如果指定区间的一部分在右子树中,递归调用ask函数处理右子树并将结果相加
	return val; // 最后返回结果变量的值作为查询的结果
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)	cin>>a[i];
	build(1,1,n);
	for(int i=1;i<=m;i++){
		int opt;
		cin>>opt;
		if(opt==1){
			int x,y,k;
			cin>>x>>y>>k;
			change(1,x,y,k);
		}
		else{
			int x,y;
			cin>>x>>y;
			ans[++tot]=ask(1,x,y);
		}
	}
	for(int i=1;i<=tot;i++)	cout<<ans[i]<<endl;
	return 0;
}

标签:int,线段,long,add,sum,区间,节点
From: https://www.cnblogs.com/Nebulary/p/17623994.html

相关文章

  • 在不完全重合的情况下,判断线经过另一线段的方法
    importlogginglogging.basicConfig(level=logging.INFO,format='%(asctime)s-%(filename)s[line:%(lineno)d]-%(levelname)s:%(message)s',datefmt='%Y-%m-%d%H:%M:%S')importnumpyasnpfromshapely.......
  • 【数据结构】线段树
    例题1:给定一个正整数数列,每一个数都在添加操作:向序列后添加一个数,序列长度变成;询问操作:询问这个序列中最后程序运行的最开始,整数序列为空。一共要对整数序列进行次操作。写一个程序,读入操作的序列,并输出询问操作的答案。数据范围这道题看第一眼:暴力,再看一眼:爆炸(bushiTLE。......
  • 【学习笔记】线段树分治
    定义线段树分治是一种解决一类有插入、删除和整体查询操作的问题的方法。它是一种离线做法,通过在线段树上记录操作的时间区间来处理修改对询问的影响。每个操作被看作一个时间区间的修改,并在线段树上进行标记。然后通过深度优先搜索(DFS)依次执行这些操作,直到根节点来回答查询,并在......
  • 线段树的一些延伸
    一.动态开点线段树简介虽然思路简单,但对于一个习惯数组写法的人,这是一个比较难受的东西。动态开点一般是用来解决空间上的问题的。一般来说,普通的线段树是直接将一颗完整的线段建出来,但如碰到数据范围大或卡空间的时候,我们就只能在我们需要的时候再建,这个就叫做动态开点。(类似......
  • ABC245E Wrapping Chocolate [线段树二分]
    也许更好的阅读体验\(\mathcal{Description}\)\(n\)个物品有长和宽,\(m\)个盒子也有长和宽,一个盒子最多可以装一个物品,问\(n\)个物品能否都放进盒子,物品和盒子不能旋转\(\mathcal{Solution}\)先离散化长和宽,将物品和盒子按照长从大到小排序考虑到当前物品时将所有长大于等于当......
  • 线段树补充
    线段树补充线段树维护矩阵和矩阵快速幂和普通快速幂同理intM;structmatrix{ llx[M+1][M+1]; matrix(){ memset(x,0,sizeof(x)); }};matrixmultiply(matrix&a,matrix&b){ matrixc; for(inti=1;i<=M;i++) for(intj=1;j<=M;j++) for(intk=1;k<=......
  • 线段树合并学习笔记
    基本思路线段树合并其实就是简单的暴力合并就可以了。一般是运用于权值线段树。通常是在每个节点都需要要一颗线段树才能维护答案,且有多个节点时,会使用线段树合并。但每个节点所有的权值不能太多,如果都是比较满的二叉树的话,时间复杂度就会很高。通常,加入值的数量跟节点数量在同......
  • 李超线段树学习笔记
    用途李超线段树的用法非常固定,一般就是让你求对于给出的一些线段或直线中,对于某个x最大的y是那个。通常可以用于斜率优化。而其的时间复杂度是\(O(n\logn^2)\)思路注:下文默认是线段,因为直线也只用改一下就行了。我们建立一颗线段树,每个节点保存在当前区间,当x=mid时最大的......
  • 【模板】线段树
    引入题目描述给定\(n\)个数\(a[1],a[2],a[3]...a[n]\),现在又下面两种操作:1.询问区间\([x,y]\)的和,并输出。2.将下标为\(x\)的数增加\(val\)。一共\(x\)此操作\(1\len,m\le100000\),保证在\(int\)范围内。方法一:暴力枚举定义数组\(a\)储存\(n\)个元素。求区间和的时间复......
  • 线段树
    线段树除了最后一层满二叉树,用堆(一维数组)来存树,一般来说,开4n的空间build(intu,intl,intr)将一段区间初始化为线段树pushup()由子节点更新父节点的信息pushdown()(懒标记)把信息递归的更新到两个子节点modify()/update()u为结点编号,更新该结点的区间最大值修改--单点修改......