【算法】线段树模板(C++)


前言

本文主要记录普通线段树的原理及实现,方便查找和使用


一、线段树作用

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

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


二、代码实现

Tips:请注意数据范围,必要的时候使用long long

1.初始化

// 节点数据结构
struct node{
    int l,r;
    int val;
}t[4*maxn+2];

// 记录原始数据
int a[maxn + 2];

// 建树
void build(int u,int l,int r){
	t[u].l=l;
	t[u].r=r;
	if(l==r){
		t[u].val=a[l];
		return;
	}
	int mid=(l+r)/2;
	build(u*2,l,mid);
	build(u*2+1,mid+1,r);
	t[u].val=t[u*2].val+t[u*2+1].val;
}

2.单点修改

// 单点修改
void modify(int u,int x,int y){ // u为节点编号,x为要修改的位置,y为要修改的值
	if(t[u].l==t[u].r){
		t[u].val=y;
		return;
	}
	int mid=(t[u].l+t[u].r)/2;
	if(x<=mid) modify(u*2,x,y);
	else modify(u*2+1,x,y);
	t[u].val=t[u*2].val+t[u*2+1].val;
}

3.区间修改

// 更新子节点
void pushdown(int u) {
	// 将lazy标记向下传给左右孩子节点
	t[u*2].lazy+=t[u].lazy;
	t[u*2+1].lazy+=t[u].lazy;
	// 更新左右孩子节点的值,为lazy标记×孩子节点表示的区间长度,记得减1
	t[u*2].val+=t[u].lazy*(t[u*2].r-t[u*2].l+1);
	t[u*2+1].val+=t[u].lazy*(t[u*2+1].r-t[u*2+1].l+1);
	// 清除该节点的lazy标记,防止重复更新
	t[u].lazy=0; 
}

// 更新父节点
void pushup(int u){
	t[u].val=t[u*2].val+t[u*2+1].val;
}

// 区间修改
void modify2(int u,int l,int r,int x){ // x为要求区间修改的值
	// 找到子区间,则不需要向下寻找
	if(l<=t[u].l && t[u].r<=r){
		t[u].lazy+=x;
		t[u].val+=x*(t[u].r-t[u].l+1);
		return;
	}
	// 若区间分布在节点表示的区间两侧,则分别查找左右孩子表示的区间
	int mid=(t[u].l+t[u].r)/2;
	// 需要查找孩子节点,要将lazy标记向下传递
	pushdown(u);
	if(l<=mid) modify2(u*2,l,r,x);
	if(mid<r) modify2(u*2+1,l,r,x);
	// 修改完成后要记得更新父节点的值
	pushup(u);
}

4.区间查询

// 区间查询
int query(int u,int L,int R){
	// 若不在节点表示的区间内,则找不到
	if(t[u].l>R||t[u].r<L) return 0;
	// 若节点正好是要查询的子区间,则直接返回该节点的值
	if(t[u].l>=L&&t[u].r<=R) return t[u].val;
	// 若要查找的区间分布在节点表示的区间两侧,则递归分别查找
	// 记得传递lazy标记,更新孩子节点的值
	pushdown(u);
	return query(u*2,L,R)+query(u*2+1,L,R);
}

5.常见的区间问题

最值问题(例:区间修改,区间最大)

// 更新父节点
void pushup(int u){
	t[u].maxv=max(t[u*2].maxv,t[u*2+1].maxv);
}

// 更新子节点
void pushdown(int u){
	t[u*2].lazy+=t[u].lazy;
	t[u*2+1].lazy+=t[u].lazy;
	t[u*2].maxv+=t[u].lazy;
	t[u*2+1].maxv+=t[u].lazy;
	t[u].lazy=0;
}

// 区间修改
void modify(int u,int l,int r,int x){
	if(t[u].l>r||t[u].r<l) return;
	if(t[u].l>=l&&t[u].r<=r){
		t[u].lazy+=x;
		t[u].maxv+=x;
		return;
	}
	int mid=(t[u].l+t[u].r)/2;
	pushdown(u);
	modify(u*2,l,r,x);
	modify(u*2+1,l,r,x);
	pushup(u);
}

// 区间查询
int query(int u,int l,int r){
	if(t[u].l>r||t[u].r<l) return -1e9;
	if(t[u].l>=l&&t[u].r<=r){
		return t[u].maxv;
	}
	int mid=(t[u].l+t[u].r)/2;
	pushdown(u);
	int ans=-0x3f3f3f;
	ans=max(ans,query(u*2,l,r));
	ans=max(ans,query(u*2+1,l,r));
	pushup(u);
	return ans;
}

总结

以上为作者总结的基本线段树的模板,有需要的读者可以收藏本篇

例题:
洛谷P3372
洛谷P3373

  • 11
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XHD_0728

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值