线段树

线段树

线段树是一种二叉树形数据结构,用于解决区间查询和区间修改问题。它将一个数组划分为若干个连续的区间,每个区间对应线段树的一个节点。通过递归地构建线段树,我们可以在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;
}
posted @ 2023-08-11 21:36  Nebulary  阅读(22)  评论(0编辑  收藏  举报