可持久化线段树/平衡树

可持久化:

指的是我们每对树做一次修改,就将其保存为一个历史版本,在以后的询问/修改中,我可以选择任意一个历史版本来询问/修改

实现:

最简单的实现思路:

最简单的思路当然是每次修改的时候都将历史版本的树copy一遍,然后对其进行修改并保存,但是这样的话,如果要留存的历史版本很多而且树也很大,很容易就会MLE

正确的实现思路:

对于一棵树来说,我们每次单点修改的时候,无非就是修改了它的左子树或者是右子树,那么对于新的版本来说,当他修改的是左子树的节点的时候,显然我们可以继续沿用历史版本的右子树,也就是只要复制并修改一棵左子树就可以了;当他修改的是右子树的节点的时候,显然我们可以继续沿用历史版本的左子树,也就是只要复制并修改一棵右子树就可以了。这就是可持久化的核心思想

实现方式:

我们每访问一个历史版本的结点的位置的时候,都先新开一个对应的结点,并让这个节点继承历史版本的左子树和右子树,然后根据我们需要修改的位置去递归他的左子树或者右子树。假设我们递归的是左子树,这样一来,当我们一访问需要更新的左子树的时候,我们又会开了一个新的结点,就达到了开新树的目的,同时,没有递归的右子树就是继承了原来历史版本的旧子树。

可持久化线段树: 

题目链接:P3919 【模板】可持久化线段树 1(可持久化数组) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

实现代码:

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1E6 + 10;
int n, m;
#define mid (l+r>>1)
int ls[N * 55], rs[N * 55], val[N * 55];
int root[N],idx;
int a[N];//点修点查不需要pushup
void build(int &u,int l,int r){//动态开点if(!u){u = ++idx;}if(l==r){val[u] = a[l];return;}build(ls[u], l, mid);build(rs[u], mid + 1, r);
}void change(int v,int &u,int l,int r,int p,int k){//开新结点u = ++idx;//递归到了叶子结点直接修改if(l==r){val[u] = k;return;}//非叶子结点继承左右子树ls[u] = ls[v];rs[u] = rs[v];//递归要修改的子树if(p<=mid){change(ls[v], ls[u], l, mid, p, k);}else{change(rs[v], rs[u], mid + 1,r, p, k);}
}//和正常的点查一样
int query(int u,int l,int r,int p){if(l==r){return val[u];}if(p<=mid){return query(ls[u], l, mid, p);}else{return query(rs[u], mid + 1,r, p);}
}int main(){scanf("%d%d", &n, &m);for (int i = 1; i <= n;i++){scanf("%d", a + i);}build(root[0], 1, n);for (int i = 1,v,op,p,value; i <= m;i++){scanf("%d%d", &v, &op);if(op==1){scanf("%d%d", &p, &value);change(root[v], root[i], 1, n, p, value);}else{scanf("%d", &p);//这里也要生成一个新的版本//一开始漏了这句话,只有24ptsroot[i] = root[v];printf("%d\n", query(root[v], 1, n, p));}}return 0;
}

可持久化平衡树:

题目链接:P3835 【模板】可持久化平衡树 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

操作和可持久化线段树类似,我这里采用的是FHQ-Treap实现的平衡树,在split的过程中会修改树的值,因此在split的时候需要开新点,由于merge的时候连接的都是split开的新点,所以merge就不用再开新点了

实现代码如下:

#include <iostream>
#include <climits>
#include <time.h>
using namespace std;
const int N = 5E5 + 10;
struct node
{int l, r;int key;int val;int size;
} tr[N * 50];
int idx, root[N];
int newnode(int v)
{tr[++idx].val = v;tr[idx].key = rand();tr[idx].size = 1;return idx;
}
void pushup(int p)
{tr[p].size = tr[tr[p].l].size + tr[tr[p].r].size + 1;
}
// split更新裂痕的时候开新树
void split(int p, int v, int &x, int &y)
{if (!p){x = y = 0;return;}if (tr[p].val <= v){x = ++idx;tr[x] = tr[p];split(tr[x].r, v, tr[x].r, y);pushup(x);}else{y = ++idx;tr[y] = tr[p];split(tr[y].l, v, x, tr[y].l);pushup(y);}
}
//split的时候,更新的裂痕结点开了新结点
//在merge的时候,只有裂痕结点会发生连接,则不需要再开新结点
int merge(int x, int y)
{if (!x || !y){return x + y;}if (tr[x].key < tr[y].key){tr[x].r = merge(tr[x].r, y);pushup(x);return x;}else{tr[y].l = merge(x, tr[y].l);pushup(y);return y;}
}
void insert(int &rt, int v)
{int x, y;split(rt, v, x, y);rt = merge(merge(x, newnode(v)), y);
}
void del(int &rt, int v)
{int x, y, z;split(rt, v, x, y);split(x, v - 1, x, z);z = merge(tr[z].l, tr[z].r);rt = merge(merge(x, z), y);
}
int getk(int &rt, int v)
{int x, y;split(rt, v - 1, x, y);int ans = tr[x].size + 1;rt = merge(x, y);return ans;
}
int getval(int p, int k)
{if (k == tr[tr[p].l].size + 1){return tr[p].val;}else if (k <= tr[tr[p].l].size){return getval(tr[p].l, k);}else{return getval(tr[p].r, k - tr[tr[p].l].size - 1);}
}
int getpre(int &rt, int v)
{int x, y;split(rt, v - 1, x, y);int z = x;if (!z){return INT_MIN + 1;}while (tr[z].r){z = tr[z].r;}rt = merge(x, y);return tr[z].val;
}
int getnxt(int &rt, int v)
{int x, y;split(rt, v, x, y);int z = y;if (!z){return INT_MAX;}while (tr[z].l){z = tr[z].l;}rt = merge(x, y);return tr[z].val;
}
// 还能优化:
// 操作数为3,4,5,6时,不需要copy一份新树
int main()
{int n;cin >> n;srand((unsigned int)time(NULL));for (int i = 1, v, opt, x; i <= n; i++){cin >> v >> opt >> x;root[i] = root[v];if (opt == 1){insert(root[i], x);}else if (opt == 2){del(root[i], x);}else if (opt == 3){cout << getk(root[i], x) << endl;}else if (opt == 4){cout << getval(root[i], x) << endl;}else if (opt == 5){cout << getpre(root[i], x) << endl;}else if (opt == 6){cout << getnxt(root[i], x) << endl;}}return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/40273.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线程池概念的详解

前言&#x1f440;~ 上一章我们介绍了什么是定时器以及如何去实现一个定时器&#xff0c;今天我们来讲解在多线程中同样很重要的一个内容线程池 线程池的出现 线程池概念 标准库中的线程池 工厂模式 newCacheThreadPool方法 newFixedThreadPool方法 ThreadPoolExecutor…

Akamai+Noname强强联合 | API安全再加强

最近&#xff0c;Akamai正式完成了对Noname Security的收购。本文我们将向大家介绍&#xff0c;经过本次收购后&#xff0c;Akamai在保护API安全性方面的后续计划和未来愿景。 Noname Security是市场上领先的API安全供应商之一&#xff0c;此次收购将让Akamai能更好地满足日益增…

图像基础知识

图像卷积 卷积(convolution)是通过两个函数f和g生成第三个函数的一种数学算子,表征函数f与g经过翻转和平移的重叠部分的面积。 卷积概念是两个变量在某范围内相乘后求和的结果。图像处理中的卷积概念:数字图像是一个二维的离散信号,对数字图像做卷积操作其实就是利用卷积…

Java进阶学习|Day4.Java多线程,线程池

文章目录 了解多线程CPU进程(Process)线程多线程开发多线程优点 实现方式继承Thread类实现Runnable接口实现Callable接口 线程状态转换线程状态线程调度调整线程优先级线程睡眠线程等待线程让步线程加入线程唤醒 线程同步线程同步方式多线程间通信 线程池了解线程池定义常见接口…

可视化作品集(02):应急预警上的应用

应急预警领域是可视化大屏的一个重要应用场景&#xff0c;大屏展示的海量数据能为应急工作提供数据支持&#xff0c;本文带大家看看这类大屏的设计。 可视化大屏在应急和预警领域有广泛的应用&#xff0c; 1. 突发事件监测和应急响应&#xff1a; 可视化大屏可以实时展示突发…

C++设计模式----命令模式

1、介绍 命令模式是⼀种⾏为型设计模式&#xff0c;其允许将请求封装成⼀个对象(命令对象&#xff0c;包含执⾏操作所需的所有信息)&#xff0c;并将命令对象按照⼀定的顺序存储在队列中&#xff0c;然后再逐⼀调用执⾏&#xff0c;这些命令也可以⽀持反向操作&#xff0c;进⾏…

Python库skbio介绍

scikit-bio(简称skbio)是一个用于生物信息学和生态学分析的Python库,提供了丰富的数据处理和分析工具。scikit-bio支持多种生物数据类型,包括序列数据、谱系树、距离矩阵等,并提供了多样性分析、序列对比和统计分析等功能。 安装scikit-bio 安装scikit-bio可以通过Pytho…

顺序表的应用——通讯录的实现

前言 本篇博客将接着上次顺序表的内容进行拓展应用&#xff0c;这次来为大家介绍通讯录的实现&#xff0c;它就是基于顺序表的结构完成的&#xff1b;如果你对此感兴趣&#xff0c;请看下面的内容&#xff1b; 1.顺序表的应用 我们前面学过&#xff0c;顺序表可以存放任意类…

Android Gradle开发与应用Gradle详细使用

一、Gradle 基础知识 1. Gradle 构建脚本 Gradle 构建脚本通常使用 Groovy 或 Kotlin DSL 编写。Android 项目中有两个主要的 Gradle 构建脚本&#xff1a; a、项目级构建脚本 (build.gradle 或 build.gradle.kts)&#xff1a;位于项目的根目录中&#xff0c;用于配置项目范…

Java | Leetcode Java题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution {public String shortestPalindrome(String s) {int n s.length();int[] fail new int[n];Arrays.fill(fail, -1);for (int i 1; i < n; i) {int j fail[i - 1];while (j ! -1 && s.charAt(j 1) ! s.charAt…

ASP.NET Core Blazor 5:Blazor表单和数据

本章将描述 Blazor 为处理 HTML 表单提供的特性&#xff0c;包括对数据验证的支持。 1 准备工作 继续使用上一章项目。   创建 Blazor/Forms 文件夹并添加一个名为 EmptyLayout.razor 的 Razor 组件。本章使用这个组件作为主要的布局。 inherits LayoutComponentBase<div …

【Mojo开发新纪元】探索CSS预处理器的集成之路

标题&#xff1a;【Mojo开发新纪元】探索CSS预处理器的集成之路 Mojolicious是一个基于Perl的现代且高性能的Web开发框架&#xff0c;它提供了一套丰富的工具来简化Web开发过程。随着前端技术的发展&#xff0c;CSS预处理器如Sass和Less因其强大的功能和灵活性&#xff0c;已经…

论文 | PRCA: 通过可插拔奖励驱动的上下文适配器拟合用于检索问答的黑盒大语言模型

论文全称&#xff1a;PRCA: Fitting Black-Box Large Language Models for Retrieval Question Answering via Pluggable Reward-Driven Contextual Adapter 核心问题&#xff1a;如何在检索增强式问答&#xff08;ReQA&#xff09;任务中&#xff0c;利用大型语言模型&#xf…

Java面试题:sql优化

表的设计的优化 参考阿里开发手册(嵩山版) 设置合适数值类型(tinyint,int,bigint) 设置合适的字符串类型(char,varchar) 索引优化 SQL语句优化 需要指明字段名称,避免直接使用select*,导致回表查询 避免造成索引失效的写法 尽量用union all代替union,union会多一次过滤…

【C语言入门】初识C语言:掌握编程的基石

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C语言 “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;C语言入门 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀C语言入门 &#x1f4d2;1. 选择…

Mac OS M3 安装 Docker 并解决芯片不支持问题

有点儿无语&#xff0c;好不容易从Docker官网下载 Apple Chip版本&#xff0c;安装挺快&#xff0c;一试废了。docker --version命令执行挺好&#xff0c;但一下载镜像常用的alpine不能运行。 经查询资料尝试选择了替代品 OrbStack目前感觉挺好用。官网&#xff1a;https://or…

在Linux系统中使用阿里云镜像源安装和配置Docker的详细教程

很多国内小伙伴在Linux上安装Docker经常会遇到网络不可达的问题。那么我们可以使用阿里云镜像来完成Docker的安装&#xff0c;这里是如何在 CentOS 上使用阿里云的 Docker 镜像源的步骤&#xff1a; 1. 删除已有的 Docker 仓库 如果之前添加了任何 Docker 仓库&#xff0c;先…

有哪些AI绘画软件?

以下是一些比较知名的AI绘画软件&#xff1a; 1、DeepArt&#xff1a;基于深度学习技术&#xff0c;可以将照片转换成各种艺术风格的绘画。 2、Artbreeder&#xff1a;通过生成对抗网络&#xff08;GAN&#xff09;&#xff0c;允许用户混合和匹配图像以创建新的艺术作品。 …

单片机关键任务优先级的实现学习

与总体产品联调时&#xff0c;需要各个单机系统严格按照总体要求&#xff0c;进行数据输出&#xff0c;时间的偏差将出现系统异常&#xff0c;控制失败等不稳定情况产生&#xff0c;甚至影响到产品安全。 因此必须确保某些关键任务的优先执行。单片机任务优先级一般有两种方式…

[小试牛刀-习题练]《计算机组成原理》之指令系统

一、选择题 0.【指令-课本习题】某计算机按字节编址&#xff0c;指令字长固定且只有两种指令格式&#xff0c;其中三地址指令29条&#xff0c;二地址指令107条&#xff0c;每个地址字段为6位&#xff0c;则指令字长至少应该是&#xff08;A&#xff09; A.24位 B. 26位 C. 28位…