【分治】线段树 SegmentTree

算法描述

线段树是一种能够处理区间修改和区间查询的数据结构。

顾名思义,线段树就是一种存储着线段数据的树形结构。它的每个节点都表示一个线段区间,每个节点的孩子节点存储的就是该区间的左半段和右半段。每个线段区间都存储着一个值,一般是区间和,也有可能是区间最大/最小值。

算法实现

线段树使用数组实现,根节点编号为 1 表示区间 1n,左子节点是 i×2,右子节点是 i×2+1.

ll lc(ll p){return p << 1;}
ll rc(ll p){return p << 1 | 1;}

初始化

从根节点开始,不断的分割区间直到该区间只剩单个数,然后开始向上汇总。传入两个变量 lr, 表示当前节点表示的线段。

void pushUp(ll p){
	sum[p] = sum[lc(p)] + sum[rc(p)];
}

void build(ll p, ll l, ll r){
	if(l == r){
		sum[p] = a[l];
		return;
	}
	ll mid = (l + r) >> 1;
	build(lc(p), l, mid);
	build(rc(p), mid + 1, r);
	pushUp(p); 
}

区间查询

从根节点开始,如果当前的线段不被查询的区间完全包含就一直分两段尝试,直到它被完全包含了,返回该线段的值然后继续递归其他的。

ll Query(ll p, ll l, ll r, ll ql, ll qr){
	if(ql <= l && qr >= r){
		return sum[p];
	}
	pushDown(p, l, r);
	ll mid = (l + r) >> 1, ans = 0;
	if(ql <= mid){
		ans += Query(lc(p), l, mid, ql, qr);
	}
	if(qr > mid){
		ans += Query(rc(p), mid + 1, r, ql, qr);
	}
	return ans;
}

区间修改

如果每次修改都从上到下全改一遍,复杂度得 O(n) ,太浪费时间了,完全体现不出线段树的优势所在。因此引入懒标记(Lazy Tag)。和查询一样,当线段被区间完全覆盖时,对应的节点自己更新,但它的孩子们不更新。这时给他安上一个懒标记,表示它的子节点需要更新的大小。当区间没有完全覆盖,需要拆分的时候,将当前变量的标记拆掉,传给子节点,这样才不会错算。

void Update(ll p, ll l, ll r, ll ql, ll qr, ll d){
	if(ql <= l && qr >= r){
		sum[p] += d * (r - l + 1);
		tag[p] += d;
		return;
	}
	pushDown(p, l, r);
	ll mid = (l + r) >> 1;
	if(ql <= mid){
		Update(lc(p), l, mid, ql, qr, d);
	}
	if(qr > mid){
		Update(rc(p), mid + 1, r, ql, qr, d);
	}
	pushUp(p);
}

复杂度估算

由于线段树原理是每个区间分两段,再将两端区间分别分段以此类推,又有 n 个元素,因此线段树的高度是 logn 的。而区间修改和查询最坏都要遍历一遍每一层,因此修改和查询的复杂度都是 Ologn 的,足以应付大部分题目。

总代码

题目:洛谷 P3372【模板】线段树 1

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
const ll MAXN = 1e5 + 5;

ll sum[4 * MAXN], a[MAXN], tag[4 * MAXN];

ll lc(ll p){return p << 1;}
ll rc(ll p){return p << 1 | 1;}

void pushUp(ll p){
	sum[p] = sum[lc(p)] + sum[rc(p)];
}

void build(ll p, ll l, ll r){
	if(l == r){
		sum[p] = a[l];
		return;
	}
	ll mid = (l + r) >> 1;
	build(lc(p), l, mid);
	build(rc(p), mid + 1, r);
	pushUp(p); 
}

void moveTag(ll p, ll l, ll r, ll t){
	sum[p] += t * (r - l + 1);
	tag[p] += t;
}

void pushDown(ll p, ll l, ll r){
	ll mid = (l + r) >> 1;
	moveTag(lc(p), l, mid, tag[p]);
	moveTag(rc(p), mid + 1, r, tag[p]);
	tag[p] = 0;
}

void Update(ll p, ll l, ll r, ll ql, ll qr, ll d){
	if(ql <= l && qr >= r){
		sum[p] += d * (r - l + 1);
		tag[p] += d;
		return;
	}
	pushDown(p, l, r);
	ll mid = (l + r) >> 1;
	if(ql <= mid){
		Update(lc(p), l, mid, ql, qr, d);
	}
	if(qr > mid){
		Update(rc(p), mid + 1, r, ql, qr, d);
	}
	pushUp(p);
}

ll Query(ll p, ll l, ll r, ll ql, ll qr){
	if(ql <= l && qr >= r){
		return sum[p];
	}
	pushDown(p, l, r);
	ll mid = (l + r) >> 1, ans = 0;
	if(ql <= mid){
		ans += Query(lc(p), l, mid, ql, qr);
	}
	if(qr > mid){
		ans += Query(rc(p), mid + 1, r, ql, qr);
	}
	return ans;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0); 
	ll n, m;
	cin >> n >> m;
	for(ll i = 1; i <= n; i ++){
		cin >> a[i];
	}
	build(1, 1, n);
	while(m --){
		ll o, x, y, k;
		cin >> o;
		if(o == 1){
			cin >> x >> y >> k;
			Update(1, 1, n, x, y, k);
		}else{
			cin >> x >> y;
			cout << Query(1, 1, n, x, y) << "\n";
		}
	}
	return 0;
}
posted @   Allenyang_Tco  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示