线段树简介

本文详细介绍了线段树的数据结构、核心操作如pushup和build,以及如何进行单点修改和区间查询。线段树用于求解数组的前缀和、修改元素值并查询区间和,具有O(logn)的时间复杂度。
摘要由CSDN通过智能技术生成

线段树

是一个二叉树,用于求出数组中一段数的和(前缀和)、给某个位置的数加上一个数、求一段区间的最大值

支持的操作:单点修改、区间查询

时间复杂度:修改和查询都是 O ( l o g n ) O(logn) O(logn)

线段树的核心操作
pushup(u):用子节点的信息来更新当前节点信息
build(u, l, r):在一段区间上初始化线段树, u表示根结点,l表示左边界,r表示右边界
modify(u, l, r):单点修改, u当前节点的编号,x要修改的位置,v要修改的值
query(u, x, v):查询一段区间的和,u表示根结点,l表示左边界,r表示右边界

线段树的储存结构
使用一维数组存节点
x的父节点编号=x/2
x的左儿子编号=2x
x的右儿子编号=2x+1

一段区间[1,2,3,4,5,6,7]的线段树表示
每一段区间里面存的都是它两个子节点的数的和

单点修改操作
在这里插入图片描述

区间查询查询
从区间[1,7]往下递归,查询a[2]~a[5]的和
在这里插入图片描述

题目

给定 n个数组成的一个数列,规定有两种操作,一是修改某个元素,二是求子数列 [a,b] 的连续和。

输入格式
第一行包含两个整数 n 和 m,分别表示数的个数和操作次数。

第二行包含 n 个整数,表示完整数列。

接下来 m 行,每行包含三个整数 k,a,b (k=0,表示求子数列[a,b]的和;k=1,表示第 a个数加 b)。

数列从 1 开始计数。

输出格式
输出若干行数字,表示 k=0时,对应的子数列 [a,b]的连续和。

输入样例:
10 5
1 2 3 4 5 6 7 8 9 10
1 1 5
0 1 3
0 4 8
1 7 5
0 4 8
输出样例:
11
30
35

题目来自acwing

代码

#include <iostream>      
#include <algorithm>      
#include <cstring>      
#include <vector>

using namespace std;

const int N = 100010;

int n, m;
int w[N];//记录一下权重
struct Node
{
	// 左右区间
	int l, r;
	// 当前区间的sum=左右两个儿子的sum之和
	int sum;
}tr[N * 4];// 开 4 倍空间,防止溢出,用一维数组储存每个结点

// 用2个子节点的信息来更新当前节点信息
void pushup(int u)
{
	// 当前节点的sum=左右儿子的sum之和 u << 1 | 1  =2x+1
	tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

// 在一段区间上初始化线段树
// 当前节点编号,左右边界
void build(int u, int l, int r)
{
	// 是叶结点
	// 叶结点的sum赋值给它自己
	if (l == r)
	{
		tr[u] = { l,r,w[r] };
	}
	else
	{
		//否则的话,说明当前区间长度至少是 2 ,那么我们需要把当前区间分为左右两个区间,那先要找边界点
		// 赋值左右边界的初值
		tr[u] = { l, r };
		int mid = (l + r) >> 1;
		// 递归左右儿子
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		// 修改完子节点之后,用子节点的信息来更新当前节点信息
		pushup(u);
	}
}


// 查询一段区间的和 u根节点编号,l r查询的区间
// 查询的过程是从根结点开始往下找对应的一个区间
int query(int u, int l, int r)
{
	// 当前区间tr[u].l~tr[u].r已经被查询区间l~r完全包含,直接返回它的值
	if (l <= tr[u].l && tr[u].r <= r)
	{
		return tr[u].sum;
	}
	//否则的话我们需要去递归来算
	//计算当前 区间的中点是多少
	int mid = (tr[u].l + tr[u].r) >> 1;
	int sum = 0;//sum 来表示一下我们的总和
	// 判断当前区间的中点和查询区间的左边有没有交集
	if (l <= mid)
	{
		// 到下一层的左边区间查找
		sum += query(u << 1, l, r);
	}
	// 判断当前区间的中点和右边有没有交集
	if (r > mid)
	{
		// 和右边有交集 右区间的左端点=mid+1
		// 到下一层的右边区间查找
		sum += query(u << 1 | 1, l, r);
	}
	return sum;
}

// 单点修改
// u当前节点的编号,x要修改的位置,v要修改的值
void modify(int u, int x, int v)
{
	// 如果当前已经是叶节点了,那我们就直接让他的总和加上 v 就可以了
	if (tr[u].l == tr[u].r)
	{
		tr[u].sum += v;
	}
	else
	{
		// 当前已经不是叶节点
		int mid = (tr[u].l + tr[u].r) >> 1;
		// 判断x在左半边还是右半边
		if (x <= mid)
		{
			// 在左半边,那就修改左儿子
			modify(u << 1, x, v);
		}
		else
		{
			// 在右半边,那就修改右儿子
			modify(u << 1 | 1, x, v);
		}
		// 更新完之后,用子节点的信息来更新当前节点信息
		pushup(u);
	}

}

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> w[i];
	}
	/*根节点的下标=1,初始区间是 1 到 n */
	build(1, 1, n);
	int k, a, b;
	while (m--)
	{
		cin >> k >> a >> b;
		if (k == 0)
		{
			// 求和,根节点编号,查询的区间 
			cout << query(1, a, b) << endl;
		}
		else
		{
			// 根节点的下标,要修改的位置,是要修改的值
			modify(1, a, b);
		}
	}
	return 0;
}
  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值