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

利用伸展树高效处理区间操作

本文探讨了如何利用伸展树(SplayTree)来高效地处理区间操作,包括区间修改、查询和删除等。通过引入size域,伸展树能够灵活应对序列结构的变化。
### 利用伸展树高效处理区间操作

考虑一个从1开始编号的序列,记作A[1...N],我们可以在其上执行多种区间操作。例如,将区间[s, e]中的所有元素增加一个值delta,查询区间[s, e]中所有元素的和或极值,甚至将区间[s, e]从原序列中删除,并将新的元素插入到指定位置。

为了高效处理这些操作,我们可以构建一棵伸展树,其中每个节点的键值为原序列的下标。这棵树的中序遍历结果即为原序列。由于伸展操作不会改变键值的顺序,因此这棵树始终与原序列一一对应,从而可以将对序列的操作转换为对伸展树的操作。

在伸展树上进行区间操作非常直观。具体来说,将s-1伸展至树根,将e+1伸展为根的右子节点,则根的右子节点的左子节点就代表了区间[s, e]。为了确保s-1和e+1的存在,可以将原序列扩展为A[0...N+1]。通过在节点上添加相应的附加信息,可以轻松解决各种区间问题。

#### size域在伸展树中的重要应用

如果在节点中直接存储下标作为键值,那么在进行区间删除操作时,某些节点的键值需要更新,这会导致O(N)的时间复杂度,超出预期的O(log N)。因此,对于需要改变序列结构的区间操作,更好的方法是不显式存储键值。

节点的键值实际上是其在序列中的相对位置,可以通过size域来隐式表示。size域记录了以该节点为根的子树中节点的数量,可以用于解决第k小的问题。例如,s-1实际上是第s-1小的下标,e+1则是第e+1小的下标。

维护size域非常简单。在进行区间删除操作时,只需更新涉及的两个节点的size域,这是一个常数时间操作。通过这种方式,伸展树可以高效地处理区间删除、区间增加、区间翻转等操作,同时仍能完成区间和、区间极值的查询以及成段修改序列元素等任务。

#### 示例代码:使用伸展树解决hdu1754

```cpp
#include
#include

int const SIZE = 200003;
int const LEFT = 0;
int const RIGHT = 1;
typedef int value_t;

struct node_t {
int parent;
int child[2];
int sn;
int size;
value_t value;
value_t peak;
} Node[SIZE];

int toUsed = 0;
int Root = 0;

inline void init() {
toUsed = Root = 0;
memset(Node, 0, sizeof(node_t));
}

inline void _pushUp(int t) {
Node[t].size = 1;
Node[t].peak = Node[t].value;
int son = Node[t].child[LEFT];
if (son) {
Node[t].size += Node[son].size;
if (Node[t].peak }
son = Node[t].child[RIGHT];
if (son) {
Node[t].size += Node[son].size;
if (Node[t].peak }
}

inline int _newNode() {
++toUsed;
memset(Node + toUsed, 0, sizeof(node_t));
return toUsed;
}

inline void _link(int p, int sn, int t) {
Node[p].child[sn] = t;
Node[t].parent = p;
Node[t].sn = sn;
}

inline void _rotate(int t) {
int p = Node[t].parent;
int sn = Node[t].sn;
int osn = sn ^ 1;
_link(p, sn, Node[t].child[osn]);
_link(Node[p].parent, Node[p].sn, t);
_link(t, osn, p);
_pushUp(p);
}

void _splay(int t, int p, int &root) {
while (Node[t].parent != p) {
int pp = Node[t].parent;
if (Node[pp].parent != p) Node[pp].sn == Node[t].sn ? _rotate(pp) : _rotate(t);
_rotate(t);
}
_pushUp(t);
if (0 == p) root = t;
}

int _kth(int root, int index) {
int son = Node[root].child[LEFT];
int ss = son ? Node[son].size : 0;
if (ss == index) return root;
return index }

void build(int &t, int parent, int sn, int s, int e, value_t const a[]) {
int mid = (s + e) >> 1;
t = _newNode();
Node[t].parent = parent;
Node[t].value = a[mid];
Node[t].sn = sn;
if (mid > s) build(Node[t].child[LEFT], t, LEFT, s, mid - 1, a);
if (mid _pushUp(t);
}

int query(int &root, int s, int e) {
int t = _kth(root, s - 1);
_splay(t, 0, root);
t = _kth(root, e + 1);
_splay(t, root, root);
return Node[Node[t].child[LEFT]].peak;
}

void modify(int &root, int idx, value_t v) {
int t = _kth(root, idx);
_splay(t, 0, root);
Node[t].value = v;
if (Node[t].peak }

int N, M;
int A[SIZE] = {0};

bool read() {
if (EOF == scanf("%d%d", &N, &M)) return false;
for (int i = 1; i <= N; ++i) scanf("%d", A + i);
A[N + 1] = 0;
return true;
}

int main() {
while (read()) {
init();
build(Root, 0, 0, 0, N + 1, A);
while (M--) {
char cmd[3];
int a, b;
scanf("%s%d%d", cmd, &a, &b);
if ('Q' == *cmd) {
printf("%d\n", query(Root, a, b));
} else {
modify(Root, a, b);
}
}
}
return 0;
}
```

通过上述方法,伸展树不仅能够高效处理静态区间问题,还能灵活应对动态变化的序列结构。
推荐阅读
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
  • 题目Link题目学习link1题目学习link2题目学习link3%%%受益匪浅!-----&# ... [详细]
  • 扫描线三巨头 hdu1928hdu 1255  hdu 1542 [POJ 1151]
    学习链接:http:blog.csdn.netlwt36articledetails48908031学习扫描线主要学习的是一种扫描的思想,后期可以求解很 ... [详细]
  • 题目描述:给定n个半开区间[a, b),要求使用两个互不重叠的记录器,求最多可以记录多少个区间。解决方案采用贪心算法,通过排序和遍历实现最优解。 ... [详细]
  • 本文详细探讨了VxWorks操作系统中双向链表和环形缓冲区的实现原理及使用方法,通过具体示例代码加深理解。 ... [详细]
  • 本题涉及一棵由N个节点组成的树(共有N-1条边),初始时所有节点均为白色。题目要求处理两种操作:一是改变某个节点的颜色(从白变黑或从黑变白);二是查询从根节点到指定节点路径上的第一个黑色节点,若无则输出-1。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本实验主要探讨了二叉排序树(BST)的基本操作,包括创建、查找和删除节点。通过具体实例和代码实现,详细介绍了如何使用递归和非递归方法进行关键字查找,并展示了删除特定节点后的树结构变化。 ... [详细]
  • 本文详细介绍了C语言中链表的两种动态创建方法——头插法和尾插法,包括具体的实现代码和运行示例。通过这些内容,读者可以更好地理解和掌握链表的基本操作。 ... [详细]
  • 本文介绍了如何通过扩展 UnityGUI 创建自定义和复合控件,以满足特定的用户界面需求。内容涵盖简单和静态复合控件的实现,并展示了如何创建复杂的 RGB 滑块。 ... [详细]
author-avatar
ygluo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有