【数据结构进阶】【算法笔记】线段树

线段树是一种高效的数据结构,常用于算法竞赛中处理区间信息,如区间查询和修改。本文介绍了线段树的基本结构、建树、区间查询和修改操作,以及懒惰标记的使用。提供了一个线段树的C++实现模板,并给出了一道相关题目及解答。通过学习,读者可以掌握线段树在区间问题中的应用。
摘要由CSDN通过智能技术生成

线段树

简介:线段树是算法竞赛中常用的用来维护 区间信息 的数据结构。

线段树可以在 O(logN) 的时间复杂度内实现单点修改区间修改区间查询区间求和求区间最大值求区间最小值)等操作。

文章只包含代码和模板例题,具体学习参考以下链接

学习链接 :OI Wiki 线段树
前置知识 :树状数组…
相关知识 :前缀和、区间问题

代码实现


线段树的基本结构

sum 结点区间的和
lazy 懒标记 (延迟标记)
l r 区间的左右端点
cnt 区间的结点数量,可以用 r - l + 1代替

struct node {
	long long sum, lazy;
	int l, r, cnt;
};
node tree[N * 4];

建树

// 建树
void built_tree(int now, int l, int r) {
	// 先把当前点能处理的信息先处理了
	tree[now].l = l, tree[now].r = r;
	tree[now].cnt = r - l + 1;
	tree[now].lazy = 0;
	// 只有一个元素
	if (l == r) {
		tree[now].sum = num[l]; // num[r] = num[l];
		return ;
	}
	// 不是只有一个元素的话,分成左右两个区间都处理完了之后,再回头处理自己的 sum 
	int mid = (l + r) / 2;
	built_tree(now * 2, l, mid);
	built_tree(now * 2 + 1, mid + 1, r);
	// 更新自身和值,抽象为函数
	up_sum(now);
}
// 左右区间都处理完后,求和值
void up_sum(int now) {
	tree[now].sum = tree[now * 2].sum + tree[now * 2 + 1].sum;
}

区间查询操作

// 区间查询操作,当前点,求解区间的左区间,求解区间的右区间
long long query(int now, int l, int r) {
	// 如果是完全包含的关系,直接返回即可
	if (tree[now].l >= l && tree[now].r <= r) {
		return tree[now].sum;
	}
	// 如果不是完全包含的关系,需要向下分解
	// 分解前向下处理懒标记
	down_lazy(now);
	// 查询分两部分c++
	int mid = (tree[now].l + tree[now].r) / 2;
	long long tsum = 0; // 从now分,对应两边的和
	if (mid >= l) tsum += query(now * 2, l, r);	// 左边有东西
	if (mid < r) tsum += query(now * 2 + 1, l, r);	// 右边有东西
	return tsum;
}

区间修改操作

// 区间修改 
void modify(int now, int l, int r, int val) {
    // 如果是完全包含的关系,直接修改即可
	if (tree[now].l >= l && tree[now].r <= r) {
		tree[now].lazy += val;	// 更新懒标记
		tree[now].sum += val * tree[now].cnt; // 更新和值
		return ;
	}
    // 如果不是完全包含的关系,需要向下分解
	// 分解前向下处理懒标记
	down_lazy(now);
	int mid = (tree[now].l + tree[now].r) / 2;
	if (mid >= l) modify(now * 2, l, r, val);
	if (mid < r) modify(now * 2 + 1, l, r, val);
	up_sum(now);
}

向下处理懒标记 lazy (延迟标记)

void down_lazy(int now) {
	// 如果当前结点的lazy不为0
	// 懒标记开始下沉
	if (tree[now].lazy != 0) {
		// 向下更新lazy
		tree[now * 2].lazy += tree[now].lazy;
		tree[now * 2 + 1].lazy += tree[now].lazy;
		// 向下更新和值
		tree[now * 2].sum += tree[now].lazy * tree[now * 2].cnt;
		tree[now * 2 + 1].sum += tree[now].lazy * tree[now * 2 + 1].cnt;
		tree[now].lazy = 0;
	}
}

线段树 模板题 OJ - Online Judge (haizeix.com)

题目描述

给定一个 n n n 位数组和两种操作:

操作1:数组中某个区间的所有数字加上一个值

操作2:查询数组中某个区间的所有数字之和

输入

第一行输入两个整数 n , m ( 1 ≤ n ≤ 10000 , 3 ≤ m ≤ 100000 ) n,m(1≤n≤10000,3≤m≤100000) n,m(1n100003m100000)​,分别代表数组大小和操作数。

第二行包含 n n n 个整数,代表数组中相应的数字,数字大小不会超过 i n t int int 表示范围。

接下来 m m m 行,每行三个或四个整数 a , b , c , d ( a ∈ [ 1 , 2 ] ) a,b,c,d(a∈[1,2]) a,b,c,da[1,2]

  1. a = 1 a=1 a=1​ 时,接下来输入 b , c , d ​ b,c,d​ b,c,d,代表将数组 [ b , c ] [b,c] [b,c]​区间内的数字加上 d , ( 1 ≤ b , c ≤ n , d i s i n t ) d,(1≤b,c≤n, d is int) d(1b,cn,disint)​,当 b > c b>c b>c 时,不做任何操作。
  2. a = 2 a=2 a=2 时,接下来输入 b , c b,c b,c,输入代表询问 [ b , c ] [b,c] [b,c] 区间内的和值 ( 1 ≤ b , c ≤ n ) (1≤b,c≤n) (1b,cn),当 b > c b>c b>c 时,输出 0 0 0
输出

对于每个 a = 2 a=2 a=2 的操作,输出查询区间内数字的和值,答案不会超过 64 64 64位整型 ( l o n g l o n g ) (long long) longlong的表示范围。

样例输入

6 5
6 9 4 3 2 1
2 2 5
1 2 3 5
2 1 6
1 5 12 3
2 1 6

样例输出

18
35
41

主函数代码

int main() {
	cin >> n >> m;	// n 元素数量,m 操作数
	for (int i = 1; i <= n; i++) {
		cin >> num[i];
	}
	built_tree(1, 1, n); // 当前所在结点,建树的区间
	
	// 获取每一种操作
	for (int i = 0; i < m; i++) {
		int t;
		cin >> t;
		// 	区间更新操作
		if (t == 1) {	
			int a, b, c;
			cin >> a >> b >> c;	// 改成scanf,不然会超时
			modify(1, a, b, c);	// 区间更新操作, 在树根1号点开始,对区间a到b整体加上c
		// else 区间查询操作
		} else {
			int a, b;
			cin >> a >> b;
			cout << query(1, a, b) << endl;
			// 从树根1号点开始找,查询区间 a到b 的区间和
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值