线段树
线段树是一种二叉树形数据结构,用于解决区间查询和区间修改问题。它将一个数组划分为若干个连续的区间,每个区间对应线段树的一个节点。通过递归地构建线段树,我们可以在O(log n)的时间复杂度内完成区间查询和区间修改操作。
原理
线段树的构建过程如下:
- 将原数组划分为n个子区间,左闭右开,例如对于数组
[1, 2, 3, 4, 5]
,划分为[1, 3]
,[2, 4]
,[3, 5]
三个子区间。 - 递归地构建左右子树。
- 将根节点的信息设置为区间的最大值、最小值、最大值索引、最小值索引等。
线段树的查询和修改过程如下:
- 查询操作:给定一个区间[l, r],在根节点中查找该区间的最大值和最小值。如果l <= 区间起始位置 <= r,则更新根节点的信息;否则递归地在左右子树中查找。
- 修改操作:给定一个区间[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