热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Splay树的高级应用:区间操作详解

在学习了Splay树的基本查找功能后,可能会觉得它与普通的二叉查找树没有太大的区别,仅仅是通过splay操作减少了时间开销。然而,Splay树之所以被誉为“序列之王”,主要在于其强大的区间操作能力。

在掌握了Splay树的基本查找功能之后,你可能会认为它与传统的二叉查找树没有太大差异,主要是通过splay操作减少了时间消耗。但Splay树被称为“序列之王”绝非偶然,其真正的优势在于高效的区间操作。

如何实现这些区间操作呢?

首先,我们知道二叉查找树的中序遍历结果是一个有序序列。在Splay树中,我们利用这一点,通过splay操作将需要操作的区间调整到特定位置,从而实现高效区间操作。

具体来说,Splay树支持以下几种操作:

1. 建树

2. 区间操作(如翻转、赋值等)

3. 输出序列

建树
对于区间的构建,我们可以借鉴线段树的思想,采用递归的方式从中点开始构建。
void build(int& u, int l, int r, int fa) { if (l > r) return; u = ++siz; int mid = (l + r) >> 1; e[u].v = mid; e[u].siz = 1; e[u].f = fa; if (l + 1 
这样递归地从中间开始建树,确保了树的平衡性。
区间操作
要对树中的某个区间进行操作,关键在于如何快速找到并调整这个区间。
Splay树的伸展操作(即splay操作)可以在不改变中序遍历顺序的前提下调整树的结构,这意味着我们可以在不改变序列的情况下改变树的形态。
例如,要操作区间 [l, r],我们可以通过splay操作将 l-1 节点旋转到根节点,将 r+1 节点旋转到根节点的右子节点。此时,r+1 节点的左子树即为所需的区间 [l, r]。
如何找到这两个节点?可以使用splay中的查找第k个节点的方法。
对于操作整个区间 [1, N],可以添加两个哨兵节点 0 和 N+1 来简化处理。
找到区间后,具体的区间操作(如翻转、赋值等)根据题目需求进行,通常会使用lazy标记来优化性能。
#define isr(x) (e[e[x].f].ch[1] == x) inline void spin(int u) { int fa = e[u].f, s = isr(u); e[u].f = e[fa].f; if (e[fa].f) e[e[fa].f].ch[isr(fa)] = u; e[fa].ch[s] = e[u].ch[s ^ 1]; e[fa].f = u; if (e[u].ch[s ^ 1]) e[e[u].ch[s ^ 1]].f = fa; e[u].ch[s ^ 1] = fa; up(fa); up(u); } void splay(int u, int fa = 0) { while (e[u].f != fa) { if (e[e[u].f].f && lazy[e[e[u].f].f]) pd(e[e[u].f].f); if (lazy[e[u].f]) pd(e[u].f); if (lazy[u]) pd(u); if (e[e[u].f].f == fa) spin(u); else if (isr(u) ^ isr(e[u].f)) spin(u), spin(u); else spin(e[u].f), spin(u); } if (!fa) root = u; }
其中,lazy是标记数组,splay时需要传递标记,任何涉及子节点的操作都应考虑标记的传递。
输出序列
输出序列实际上就是进行中序遍历,具体实现可以自行编写。
示例题目
一个典型的区间操作题目是洛谷P3391 文艺平衡树,主要涉及区间翻转。
#include  #include  #include  #define isr(x) (e[e[x].f].ch[1] == x) using namespace std; const int maxn = 200005, INF = 2000000000; int N, M; inline int read() { int out = 0, flag = 1; char c = getchar(); while (c <48 || c > 57) { if (c == '-') flag = -1; c = getchar(); } while (c >= 48 && c <= 57) { out = out * 10 + c - 48; c = getchar(); } return out * flag; } int lazy[maxn]; class node { public: int v, ch[2], f, siz; node() { ch[0] = ch[1] = 0; siz = 0; } }; node e[maxn]; int siz = 0, root = 0; inline void up(int u) { e[u].siz = e[e[u].ch[0]].siz + e[e[u].ch[1]].siz + 1; } inline void pd(int u) { swap(e[u].ch[0], e[u].ch[1]); lazy[e[u].ch[0]] ^= 1; lazy[e[u].ch[1]] ^= 1; lazy[u] ^= 1; } inline void spin(int u) { int fa = e[u].f, s = isr(u); e[u].f = e[fa].f; if (e[fa].f) e[e[fa].f].ch[isr(fa)] = u; e[fa].ch[s] = e[u].ch[s ^ 1]; e[fa].f = u; if (e[u].ch[s ^ 1]) e[e[u].ch[s ^ 1]].f = fa; e[u].ch[s ^ 1] = fa; up(fa); up(u); } void splay(int u, int fa = 0) { while (e[u].f != fa) { if (e[e[u].f].f && lazy[e[e[u].f].f]) pd(e[e[u].f].f); if (lazy[e[u].f]) pd(e[u].f); if (lazy[u]) pd(u); if (e[e[u].f].f == fa) spin(u); else if (isr(u) ^ isr(e[u].f)) spin(u), spin(u); else spin(e[u].f), spin(u); } if (!fa) root = u; } int kth(int u, int k) { if (lazy[u]) pd(u); if (k == e[e[u].ch[0]].siz + 1) return u; else if (k > e[e[u].ch[0]].siz + 1) return kth(e[u].ch[1], k - e[e[u].ch[0]].siz - 1); return kth(e[u].ch[0], k); } void change(int l, int r) { int L = kth(root, l), R = kth(root, r + 2); splay(L); splay(R, root); lazy[e[e[root].ch[1]].ch[0]] ^= 1; } void build(int& u, int l, int r, int fa) { if (l > r) return; u = ++siz; int mid = (l + r) >> 1; e[u].v = mid; e[u].siz = 1; e[u].f = fa; if (l + 1 = 1 && e[u].v <= N) printf("%d ", e[u].v); print(e[u].ch[1]); } } int main() { N = read(); M = read(); int a, b; build(root, 0, N + 2, 0); while (M--) { a = read(); b = read(); if (a == b) continue; change(a, b); } print(root); cout <

推荐阅读
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文介绍如何使用 Python 将一个字符串按照指定的行和元素分隔符进行两次拆分,最终将字符串转换为矩阵形式。通过两种不同的方法实现这一功能:一种是使用循环与 split() 方法,另一种是利用列表推导式。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • Java 中 Writer flush()方法,示例 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 本文详细介绍了IBM DB2数据库在大型应用系统中的应用,强调其卓越的可扩展性和多环境支持能力。文章深入分析了DB2在数据利用性、完整性、安全性和恢复性方面的优势,并提供了优化建议以提升其在不同规模应用程序中的表现。 ... [详细]
  • QUIC协议:快速UDP互联网连接
    QUIC(Quick UDP Internet Connections)是谷歌开发的一种旨在提高网络性能和安全性的传输层协议。它基于UDP,并结合了TLS级别的安全性,提供了更高效、更可靠的互联网通信方式。 ... [详细]
  • 如何在PHPcms网站中添加广告
    本文详细介绍了在PHPcms网站后台添加广告的方法,涵盖多种常见的广告形式,如百度广告和Google广告,并提供了相关设置的步骤。同时,文章还探讨了优化网站流量的SEO策略。 ... [详细]
author-avatar
会丶有那么一天
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有