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

可持久化数据结构-线段树(主席树)

可持久化数据结构-线段树(主席树)

(与可持久化字典树差不多)

  1. 概念:可持久化线段树是基本线段树的一个简单拓展, 是使用函数式编程思想的线段树;

  2. 作用: 可以存下来数据结构的所有历史版本

  3. 特点: 拓扑结构不变

  4. 核心思想:只记录每一个版本与前一个版本不同的节点, 并且利用历史版本之间的共用数据减少时间和空间消耗 (听起来很懵)

  5. 打个比喻: 1 s 1s 1s 动画由 20 20 20帧左右的静态画面连续播放而成, 每两幅相邻画面之间的差别很少, 如果用计算机制作动画 , 若每一帧的画面都重新制作,会很浪费空间, 为了降低成本, 让每一帧画面只记录与前一帧的不同处;(又如红绿灯的秒数倒计时)

  6. 类比线段树:基本思路如下:
    (1).有多棵线段树–如同每一帧的画面,(特点)
    (2).相邻的两棵线段树之间差别很小, 所以每棵线段树只需存储于前一棵的不同之处, 使用时再去修改填补并生成一棵完整存线段树(复制)。
    (3).任意两颗线段树能"相减", 得到一棵新的线段树, 它往往包含了题目需要的解。

    实例:

    ​ 给定 n n n 个整数构成的序列 a a a,将对于指定的闭区间 [ l , r ] [l, r] [l,r] 查询其区间内的第 k k k 小值。

    如何解决?

    一种可行的方案是:使用主席树。 主席树的主要思想就是:保存每次插入操作时的历史版本,以便查询区间第 k k k 小。

    ​ 若每次开一棵线段树去存储, 很明显空间会爆掉。那应该如何去做呢?

    ​ 分析一下, 发现每次操作修改的的点的个数都是一样的。 如下图, 修改了 [ 1 , 8 ] [1, 8] [18] 中对应权值为 1 的结点,红色的点即为更改的点

    只更改了 l o g n log_n logn 个结点,形成一条链,也就是说每次更改的结点数 = 树的高度

    怎么保存呢?简单暴力一点,每次开一棵线段树呗。
    那空间还不爆掉?

  7. 需要建多少棵树呢?
    题目给定包含 n 个元素的序列, 每次用一个新元素建成一棵线段树, 共有 n 棵线段树

  8. 每棵树有多少节点?
    线段树的叶子节点记录了元素, 如果元素没有重复, 叶子节点就设为 n 个, 如果元素有重复, 根据情况, 叶子节点可以设为 n 个, 也可以设为不重复元素的数量

  9. 注意, 可持久化线段树只能解决单点修改问题, 不可解决区间修改问题

原因:不好处理懒标记

1.空间复杂度呈指数级增长

​ 当使用常规的可持久化线段树进行区间修改时,假设我们要对区间进行修改。如果使用懒标记来处理区 间修改,每次更新时,为了维护可持久化的特性,需要复制从根节点到包含该区间的所有节点路径。

例如,对于一个深度为 $ h$ 的线段树,在最坏的情况下,区间修改可能会导致 $2^h $ 个节点被复制。这 是因为线段树的节点数与区间划分的层次有关,一般有个节点(为数据规模)。如果频繁进行区间修改,空间消耗会急剧上升。

2. 时间复杂度的增加
  • 时间复杂度方面,每次区间修改涉及的节点复制和更新操作是比较复杂的。首先,要找到包含区间的节点
  • 然后,在复制和更新节点时,由于要维护可持久化,对于每个复制的节点,可能还需要更新其相关的指针和数据。例如,更新节点的左右子节点指针、更新区间范围信息等。
  • 当懒标记下传时,还需要对每个子节点进行相应的更新操作,这个过程在最坏情况下可能会导致每次区间修改的时间复杂度达到。而且在进行查询操作时,由于节点结构因为区间修改变得更加复杂,查询的时间复杂度也可能会受到影响,不再是简单的,可能会因为需要处理更多的节点和懒标记而增加。
3. 实际逻辑复杂度

​ 太麻烦了没必要

  1. 实际解决: 通常需要结合前缀和巧妙的去处理, 通过预处理达到 O ( 1 ) O(1) O(1) 的复杂度。 So, 如果需要得到 [ l , r ] [l, r] [l,r] 的统计信息,只需要用 [ 1 , r ] [1, r] [1,r] 的信息减去 [ 1 , l − 1 ] [1, l - 1] [1,l1] 的信息就行了。
  2. 存点:

不能使用堆式存储法,就是说不能用 x × 2 x \times 2 x×2 x × 2 + 1 x \times 2 + 1 x×2+1 来表示左右儿子,而是应该动态开点,并保存每个节点的左右儿子编号。 为什么这么做呢?------- 堆式存储法通常用于完全二叉树,如堆这种数据结构。在堆中,节点的存储顺序是按照数组下标来确定父子关系的。

主席树的结构特点决定了它不适合堆式存储。主席树在更新过程中,由于要保留历史版本,它的节点修改和新增是比较灵活的,不是像堆那样有固定的完全二叉树结构。

例如,在主席树中插入一个新元素可能只涉及到一条从根节点到叶子节点的路径上的节点修改和复制,而堆式存储的结构调整方式会干扰这种局部的、有针对性的更新,使得更新操作变得复杂,而且会破坏主席树原本高效的可持久化实现机制。同时,堆式存储法难以满足主席树在查询历史版本等操

作时的要求,因为它的存储方式不便于快速定位和访问历史版本对应的树结构。

------------所以, 应该用指针的方式去存

12.可持久化线段树的信息

struct{int l, r; // 表示左右子节点的下标int cnt; // 当前区间中一共有多少个数 }

一般不需要存储左右边界, 而是在递归的时候直接把左右边界直接传下去就好了。

可持久化线段树比Trie更简单,Trie在加入一个新单词可能会出现很多新的点的,但线段树中每一个版本的节点都是一样的, 所以线段树的判断要少一些。

  1. 例题

洛谷P3834 【模板】可持久化线段树 2

ACWing 255 第 k 小数

分析: 此问题为静态问题 (在整个询问当中原序列没有发生变化)

​ 数据比较大, 所以需要离散化一下, 使数据变为 0 ~ n - 1 很小的范围

接着 在数值上面建立线段树 (注意不是下标), 用线段树维护每一个数值区间中一共有多少个数

引子:如何求整体的第k小数。

已经将0 ~ n - 1离散化,排序了。

在线段树上面做二分。 判断0 - mid之间有多少个数。假设有cnt个数 如果 c n t ≥ k cnt \ge k cntk, 说明左边整个区间里面数的个数比k要多,那就说明第k小数一定是在区间的左边 则递归左边, 否则递归右边。可以发现每次只会递归一边, 整个树有 l o g n log_n logn 层,所以整个做法最多只会访问 l o g n log_n logn 个节点.

复杂度为 $ O(log_n) $ . 这个问题是很简单的。

​ 现在考虑区间限制。

[ l , r ] 之间第 k 小数为多少 [l, r] 之间第k小数为多少 [l,r]之间第k小数为多少

​ 先考虑 [ 1 , r ] [1, r] [1,r]之间第k小数为多少。用可持久化线段树,从前往后加, 找刚好加完前 r 个数的时候线段树版本为多少, 直接搜此版本的线段树即可。

​ 由于可持久化线段树的特点,树本身结构不变,

所以可以想到

​ 求出第 r 个版本的区间 [ 1 , r ] [1, r] [1,r] 之间数的个数 cnt1,

​ 以及第l - 1个版本的区间 [ l , r ] [l, r] [l,r] 之间的个数 cnt2; (前缀和的思想)

​ 用 cnt1 - cnt2 就能得到在 l ~ r 中 出现多少个数 (对应6. (3))

​ 用二分实现,就能找到最终的解。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>using namespace std;const int N = 100010;int n, m;
int a[N];
vector<int> nums; //用来离散化struct Node  //可持久化线段树的信息
{int l, r;int cnt;
}tr[N * 4 + N * 17];//首先需要开N << 2个点, 需要操作n次, 因此需要开nlogn个int root[N], idx;//每个版本的根节点 和 当前线段树中可用节点的下标//求每个数离散化后的值为多少
int find(int x)
{return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}
//建树
int build(int l, int r)
{int p = ++ idx;if (l == r) return p;int mid = l + r >> 1;tr[p].l = build(l, mid), tr[p].r = build(mid + 1, r);return p;
}
//插入
int insert(int p, int l, int r, int x)
{int q = ++ idx;tr[q] = tr[p];//复制之前的节点if (l == r){tr[q].cnt ++ ;return q;}int mid = l + r >> 1;if (x <= mid) tr[q].l = insert(tr[p].l, l, mid, x);// 在左边else tr[q].r = insert(tr[p].r, mid + 1, r, x);// 在右边tr[q].cnt = tr[tr[q].l].cnt + tr[tr[q].r].cnt;// 更新 cnt = 左右儿子之和return q;
}int query(int q, int p, int l, int r, int k)//后面一个版本为q, 前面一个版本为p;
{if (l == r) return r;//到叶子节点int cnt = tr[tr[q].l].cnt - tr[tr[p].l].cnt;//左半边的个数int mid = l + r >> 1;//查询 正如前面分析所说if (k <= cnt) return query(tr[q].l, tr[p].l, l, mid, k);else return query(tr[q].r, tr[p].r, mid + 1, r, k - cnt);
}int main()
{scanf("%d%d", &n, &m);for (int i = 1; i <= n; i ++ ){scanf("%d", &a[i]);nums.push_back(a[i]);}// 离散化 排序 + 删掉重复元素sort(nums.begin(), nums.end());nums.erase(unique(nums.begin(), nums.end()), nums.end());//第一个版本的线段树root[0] = build(0, nums.size() - 1);// n次操作for (int i = 1; i <= n; i ++ )root[i] = insert(root[i - 1], 0, nums.size() - 1, find(a[i]));// 第i 个版本的线段树, 和上一个版本的线段树比较。 0, num.size() - 1 为左右边界;//查询操作while (m -- ){int l, r, k;scanf("%d%d%d", &l, &r, &k);printf("%d\n", nums[query(root[r], root[l - 1], 0, nums.size() - 1, k)]);//返回为去掉离散化之后的值}return 0;
}

完结。

相关文章:

可持久化数据结构-线段树(主席树)

可持久化数据结构-线段树(主席树&#xff09; (与可持久化字典树差不多&#xff09; 概念&#xff1a;可持久化线段树是基本线段树的一个简单拓展&#xff0c; 是使用函数式编程思想的线段树&#xff1b; 作用&#xff1a; 可以存下来数据结构的所有历史版本 特点: 拓扑结构…...

如何利用PHP爬虫按关键字搜索淘宝商品

在当今的电商时代&#xff0c;获取淘宝商品信息对于市场研究、价格监控和竞争分析等方面具有重要意义。手动搜索和整理大量商品信息不仅耗时耗力&#xff0c;而且容易出错。幸运的是&#xff0c;PHP爬虫技术为我们提供了一种高效、自动化的方式来按关键字搜索淘宝商品。本文将详…...

GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator

GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator 操作手册 $ apt-get install device-tree-compiler libboost-regex-dev libboost-system-dev $ mkdir build $ cd build $ ../configure --prefix$RISCV $ make $ [sudo] make install 具体安装 …...

ubuntu开机启动服务

需求背景&#xff1a; 需要监控日志&#xff0c;每次都是手动启动 nohup ./prometheus >/dev/null & nohub ./node_exporter >/dev/null & 需求目标&#xff1a; 重启后系统自动启动服务...

电子电气架构 --- 设计车载充电机的关键考虑因素

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 简单,单纯,喜欢独处,独来独往,不易合同频过着接地气的生活,除了生存温饱问题之外,没有什么过多的欲望,表面看起来很高冷,内心热情,如果你身…...

2025_0105_生活记录

3号去内蒙看了流星雨。还记得上次看流星的时间是2018年&#xff0c;也是冬天&#xff0c;大家在雁栖湖校区的操场上仰望星空。那个时候幸运的看到了一颗流星&#xff0c;便迅速地在心里许愿。这次看到了三颗流星&#xff0c;我也许了愿&#xff0c;希望实现。 24年走过了十多个…...

电池管理系统(BMS)架构详细解析:原理与器件选型指南

BMS&#xff08;电池管理系统&#xff09;架构详细讲解 从你提供的BMS&#xff08;Battery Management System&#xff09;架构图来看&#xff0c;主要涉及到电池监控模块、通信模块、功率控制模块等部分。下面我将详细讲解该架构的各个功能模块及其工作原理。 1. 电池管理核…...

用JAVA编写一个简单的小游戏

用Java语言编写一个简单的小游戏。这里是一个非常基础的猜数字小游戏的代码示例。在这个游戏中&#xff0c;程序会随机选择一个1到100之间的整数&#xff0c;玩家需要猜测这个数字是什么。每次猜测后&#xff0c;程序会告诉玩家他们猜的数字是太高了、太低了还是正确。 impor…...

【SpringSecurity】二、自定义页面前后端分离

文章目录 1、用户认证流程AuthenticationSuccessHandler AuthenticationFailureHandlerSecurityFilterChain配置用户认证信息 2、会话并发处理2.1、实现处理器接口2.2、SecurityFilterChain配置 1、用户认证流程 AuthenticationSuccessHandler AuthenticationFailureHandler …...

小兔鲜儿:头部区域的logo,导航,搜索,购物车

头部&#xff1a;logo ,导航&#xff0c;搜索&#xff0c;购物车 头部总体布局: 设置好上下外边距以及总体高度&#xff0c; flex布局让总体一行排列 logo&#xff1a; logo考虑搜索引擎优化&#xff0c;所以要使用 h1中包裹 a 标签&#xff0c;a 里边写内容&#xff08;到时候…...

什么是VLAN?

VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;是一种将物理局域网划分成多个逻辑上独立的虚拟网络的技术。VLAN不依赖于设备的物理位置&#xff0c;而是通过逻辑划分&#xff0c;将局域网内的设备虚拟地组织到同一组。这种技术允许网络管理员…...

WPS计算机二级•数据查找分析

听说这里是目录哦 通配符&#x1f30c;问号&#xff08;?&#xff09;星号&#xff08;*&#xff09;波形符&#xff08;~&#xff09; 排序&#x1f320;数字按大小排序以当前选定区域排序以扩展选定区域排序 文字按首字母排序 快速筛选分类数据☄️文字筛选数字筛选颜色筛选…...

计算机网络 (28)虚拟专用网VPN

前言 虚拟专用网络&#xff08;VPN&#xff09;是一种在公共网络上建立私有网络连接的技术&#xff0c;它允许远程用户通过加密通道访问内部网络资源&#xff0c;实现远程办公和安全通信。 一、基本概念 定义&#xff1a;VPN是一种通过公共网络&#xff08;如互联网&#xff09…...

【Python学习(七)——序列、列表、元组、range、字符串、字典、集合、可变类型不可变类型】

Python学习&#xff08;七&#xff09;——序列、列表、元组、range、字符串、字典、集合、可变类型&不可变类型 本文介绍了序列、列表、元组、range、字符串、字典、集合、可变类型&不可变类型&#xff0c;仅作为本人学习时记录&#xff0c;感兴趣的初学者可以一起看…...

MATLAB常用建模方法——常用非参数检验

常用非参数检验 在用样本数据对正态总体参数作出统计判断&#xff08;例如参数估计和假设检验&#xff09;时&#xff0c;要求样本数据应服从正态分布&#xff0c;这种数据分布类型已知的总体参数的假设检验称为参数假设检验。 与参数假设检验相对应的还有非参数假设检验&#…...

【多线程初阶篇 ²】创建线程的方式

目录 二、多线程代码 1.继承Thread类 2.实现Runnable接口 3.匿名内部类 3.1 创建Thread⼦类对象 3.2 创建Runnable⼦类对象 4.lambda表达式&#xff08;推荐&#xff09; 小结&#xff1a; &#x1f525;面试题&#xff1a;Java中创建线程都有哪些写法 二、多线程代码 …...

纵览!报表控件 Stimulsoft Reports、Dashboards 和 Forms 2025.1 新版本发布!

Stimulsoft 2025.1 新版发布&#xff0c;旨在增强您创建报告、仪表板和 PDF 表单的体验&#xff01;此最新版本为您带来了许多改进和新功能&#xff0c;使数据处理更加高效和用户友好。亮点包括对 .NET 9 的支持、Microsoft Analysis Services 的新数据适配器、发布向导中适用于…...

游戏引擎学习第75天

仓库:https://gitee.com/mrxiao_com/2d_game_2 Blackboard: 处理楼梯通行 为了实现楼梯的平滑过渡和角色的移动控制&#xff0c;需要对楼梯区域的碰撞与玩家的运动方式进行优化。具体的处理方式和遇到的问题如下&#xff1a; 楼梯区域的过渡&#xff1a; 在三维空间中&#x…...

Java 23 集合框架详解:Set 接口及实现类(HashSet、TreeSet、LinkedHashSet)

&#x1f4da; Java 23 集合框架详解&#xff1a;Set 接口及实现类&#xff08;HashSet、TreeSet、LinkedHashSet&#xff09; &#x1f4d6; 概述 Set 是 Java 集合框架中用于存储 无序、不重复元素 的接口。它的实现类包括 HashSet、TreeSet 和 LinkedHashSet&#xff0c;它…...

ARMv8架构 CortexR52+ 内核 coresight_soc400介绍

前言&#xff1a;笔者在工作中接触到了一款多核芯片&#xff0c;其采用的处理器为CortexR52&#xff0c;使用的架构为ARMv8&#xff0c;我通过CoreSight SOC-400组件完成了对该芯片烧录代码的开发。这里芯片型号就不透露了&#xff0c;本文仅介绍我自己从ARM官网上提供的R52核等…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

超短脉冲激光自聚焦效应

前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应&#xff0c;这是一种非线性光学现象&#xff0c;主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场&#xff0c;对材料产生非线性响应&#xff0c;可能…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

基础测试工具使用经验

背景 vtune&#xff0c;perf, nsight system等基础测试工具&#xff0c;都是用过的&#xff0c;但是没有记录&#xff0c;都逐渐忘了。所以写这篇博客总结记录一下&#xff0c;只要以后发现新的用法&#xff0c;就记得来编辑补充一下 perf 比较基础的用法&#xff1a; 先改这…...

Device Mapper 机制

Device Mapper 机制详解 Device Mapper&#xff08;简称 DM&#xff09;是 Linux 内核中的一套通用块设备映射框架&#xff0c;为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程&#xff0c;并配以详细的…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

网站指纹识别

网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件&#xff08;web容器&#xff09;、脚本语言、数据厍 为什么要了解这些&#xff1f;举个例子&#xff1a;发现了一个文件读取漏洞&#xff0c;我们需要读/etc/passwd&#xff0c;如…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...