当前位置: 首页 > news >正文

算法详解——线段树

1. 线段树介绍

线段树是一个高度平衡二叉树,它主要用来高效动态地管理一个序列。线段树叶子结点存储序列元素值,分支结点存储一个连续地子区间的某种聚合信息,例如最值、均值等信息。

如图所示:
在这里插入图片描述

用这样一个树状结构来管理序列有什么好处?

  1. 快速地查询某个区间范围,时间复杂度为O(logn)
  2. 快速修改某个元素的值并更新分支结点,时间复杂度为O(logn)
  3. 打上延迟标记之后,可以快速修改某个范围的元素值

线段树的主要操作包括:

  1. 构建线段树
    构建线段树的时间复杂度为 O(n)O(n)O(n),通常是递归构建:
  • 如果区间只有一个元素,那么该节点是叶子节点,直接存储该元素的值。
  • 如果区间有多个元素,则将区间一分为二,递归构建左右子树,节点值为左右子树值的组合。
  1. 区间查询
    查询操作的时间复杂度为 O(log⁡n)O(\log n)O(logn):
  • 如果查询的区间完全覆盖当前节点区间,则直接返回该节点的值。
  • 如果查询的区间与当前节点区间部分重叠,则递归查询左右子树,并将左右子树的结果组合。
  1. 区间更新
    更新操作的时间复杂度同样为 O(log⁡n)O(\log n)O(logn):
  • 如果更新区间完全覆盖当前节点区间,则更新当前节点的值。
  • 如果更新区间与当前节点区间部分重叠,则递归更新左右子树,并将左右子树的结果组合更新当前节点的值。

2. 算法实战

题目如下:

3165. 不包含相邻元素的子序列的最大和

给你一个整数数组 nums 和一个二维数组 queries,其中 queries[i] = [posi, xi]

对于每个查询 i,首先将 nums[posi] 设置为 xi,然后计算查询 i 的答案,该答案为 nums不包含相邻元素的子序列的最大和。

返回所有查询的答案之和。

由于最终答案可能非常大,返回其对 109 + 7 取余 的结果。

子序列 是指从另一个数组中删除一些或不删除元素而不改变剩余元素顺序得到的数组。

分析:如果题目只是单纯的求不包含相邻元素的子序列的最大和,那么该题解法就类似于198.打家劫舍,使用动态规划即可解决。但是题目中给了多次查询,每次查询都会改变序列中一个元素的值,这意味着每次查询都需要从头开始进行计算,显然会超时。

我们可以注意到,如果序列中xi的值被改变,那么其两边子区间的解是不变的,也就是说,我们可以存储许多个子区间的解,当某个元素被修改时只需要更新相应的子区间即可,大大减少计算量。

这样一分析,可以选择使用线段树来保存和维护这些子区间。

使用线段树涉及到如何根据两个子结点的值计算父结点的值,也就是如何合并两个区间的解。如果xi被选择,则xi-1和xi+1都不能被选择,因此合并两个区间时我们需要知道两个区间左右两边的结点是否被选择了。

2.1 定义线段树结点的结构体

首先定义线段树结点的结构体:

struct SegNode
{long long best(){return v11;}void set(long long value){v00 = v01 = v10 = 0;// v11代表的可选可不选,因此如果value为负数就不选v11 = max(value, 0LL);}long long v00, v01, v10, v11;
};

其中v00代表两边结点都不被选择的情况下啊区间的解,而v01则代表左结点不被选择、右结点可选择也可不选择的情况下啊的区间解,v10和v11的含义以此类推。

2.2 定义线段树的主要操作

// 合并两个子区间
void pushup(vector<SegNode> &tree, int x)
{const SegNode &left = tree[2 * x];const SegNode &right = tree[2 * x + 1];tree[x].v00 = max(left.v01 + right.v00, left.v00 + right.v10);tree[x].v10 = max(left.v11 + right.v00, left.v10 + right.v10);tree[x].v01 = max(left.v01 + right.v01, left.v00 + right.v11);tree[x].v11 = max(left.v11 + right.v01, left.v10 + right.v11);
}// 初始化线段树
void segtree_init(vector<SegNode> &tree, vector<int> &nums, int x, int l, int r)
{if (l == r){tree[x].set(nums[l - 1]);return;}int mid = (l + r) / 2;segtree_init(tree, nums, 2 * x, l, mid);segtree_init(tree, nums, 2 * x + 1, mid + 1, r);pushup(tree, x);
}// 更新某个元素
void update(vector<SegNode> &tree, int x, int l, int r, int pos, int value)
{if (l == r){tree[x].set(value);return;}else{int mid = (l + r) / 2;if (pos <= mid)update(tree, 2 * x, l, mid, pos, value);elseupdate(tree, 2 * x + 1, mid + 1, r, pos, value);     pushup(tree, x);}
}

线段树的主要操作还包括查询某个子区间,但是本题只需要全局的解。

2.3 利用线段树解题

class Solution {
public:int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {int n = nums.size();// 由于元素个数不一定组成一个满二叉树,所以线段树的结点总数可能超过2 * nvector<SegNode> tree(4 * n + 1);segtree_init(tree, nums, 1, 1, n);constexpr int mod = 1e9 + 7;int ans = 0;for (auto &q : queries){update(tree, 1, 1, n, q[0] + 1, q[1]);ans = (tree[1].best() + ans) % mod;}return ans;}
};

http://www.mrgr.cn/news/62448.html

相关文章:

  • 什么是目标检测?
  • 【系统设计】深入了解软件的架构模式:全面指南
  • 最新出炉!2024年邮件营销平台综合盘点
  • 认知战认知作战:三藩之乱中的认知作战分析与策略
  • 乘云而上,OceanBase再越山峰
  • 基于springboot的社区团购系统设计与实现
  • UBUNTU查看CPU核心数
  • GB/T 28046.2-2019 道路车辆 电气及电子设备的环境条件和试验 第2部分:电气负荷(4)
  • 代购系统的开发与应用
  • 在canon的生活
  • docker设置加速
  • 甲方AK/SK泄漏的修复和治理
  • 未来的AI数据中心网络构架是什么样的?
  • 【django】Django REST Framework 构建 API:APIView 与 ViewSet
  • 搭建SRS流媒体服务器处理多路无人机视频流
  • Vue addComponent
  • Java 的内存管理与垃圾回收机制(23/30)
  • 配置 GreptimeDB 作为夜莺监控数据源,无缝替代 Prometheus/VictoriaMetrics
  • uni 自定义组件的生命周期(自用)
  • MySQL_客户端工具建库.
  • redis模板的应用:自定义redisTemplate序列化规则 (RedisTemplate和StringRedisTemplate)
  • 刘艳兵-DBA015-对于属于默认undo撤销表空间的数据文件的丢失,哪条语句是正确的?
  • 怎么选开放式耳机好?热门爆款开放式耳机推荐!
  • Unity XR Interaction Toolkit 开发教程(1):OpenXR 与 XRI 概述【3.0 以上版本】
  • 黑马软件测试第二篇_功能测试
  • 前端八股文第五篇