首页 > 其他分享 >简单萌萌哒 Top Tree(上)

简单萌萌哒 Top Tree(上)

时间:2024-08-27 10:26:16浏览次数:9  
标签:Tree 萌萌 rake compress tree text Top

前情提要

Top Cluster 分解与 Top Tree

情景导入

我们总是想要以一种合适的方式对树进行划分,但是对于菊花图而言,基于点的划分总是不合适的,这启发我们基于边进行划分。事实上可以证明,基于边的划分总是可行的。

Top Cluster 分解就是一种基于边的划分方式,下面我们来介绍他。

定义:

  • 一个簇可以表示为一个三元组 \((u,v,E)\),其中 \(u,v\) 为树的节点,称为簇的界点(或端点),\(E\) 为一个边集,表示该簇包含的边,路径 \((u,v)\) 称为簇路径
  • 对于树的一条边 \((u,v)\),\((u,v,\{(u,v)\})\) 是一个簇。
  • 对于簇 \(a=(u,v,E_a)\),簇 \(b=(u,w,E_b),E_a\cap E_b=\varnothing\),记 \(\text{rake}(a,b)=(u,v,E_a\cup E_b)\),也可以写成 \(\text{rake}(w)\)。
  • 对于簇 \(a=(u,v,E_a)\),簇 \(b=(v,w,E_b),E_a\cap E_b=\varnothing\),记 \(\text{compress}(a,b)=(u,w,E_a\cup E_b)\),也可以写成 \(\text{compress}(v)\)。

其中 rake 和 compress 是树收缩的两种基本操作。形象的理解,一个簇可以看成一条边,rake 就是把一条边“拍”到另一条边上,compress 就是把两条边“拼”成一条;rake 是去掉一度点,compress 是去掉二度点。如下图:

image

显然,一棵树可以不断地进行这两种操作来合并为一个簇。一种方法是,不断地 rake 来剥掉叶子。

进行若干次收缩后,用簇路径来代替原来的边,可以得到一个树形结构(下图左侧),称为收缩树;还可以得到一棵二叉树(下图右侧),每个点代表一个簇,其两个儿子表示 compress 或 rake 操作的两个簇。我们把这棵二叉树称为 Top Tree。每个叶子节点是一条边,称为基簇;其根节点表示整棵树的簇,称为根簇

image

(右图:圆点:rake 而来的;方点:不变 / compress 而来的;点上的两个小写字母表示这个簇的界点)

静态 Top Tree 的构建

为了方便问题的解决,我们希望 Top Tree 的深度是 \(O(\log n)\) 的。下面介绍一种构建方法:

首先注意到,簇的合并方式 与 轻重链剖分 有某种共性:compress 相当于重链上的 push_up,rake 相当于轻子树信息的合并。所以我们对原树进行轻重链剖分,dfs 地自底向上处理每条重链,并认为每个轻子树已经合并为一个簇。

对于重链上的每个点 \(u\),把 \(\text{fa}(u)\) 的轻子树的簇 rake 到边 \((u,\text{fa}(u))\),这里需要分治地 rake 保证深度不会太大,称这样操作形成的树为 rake 树。

然后我们得到了排成一条链的簇,同样分治地 compress 这些簇来保证深度,称这样操作形成的树为 compress 树。(称 rake 树上的点为 rake node,compress 树上的点为 compress node。)

显然,这样合并得到的 Top Tree 的深度为 \(O(\log^2 n)\)。如何优化?

根据前情提要,显然借用“全局平衡二叉树”的思想,在分治 compress 或 rake 时按簇的大小选择带权中点作为分治中点,这样 Top Tree 的深度就为 \(O(\log n)\) 了。

按这样构建出的 Top Tree 拥有一些很好的性质:

  • 所有簇的界点都有祖先关系。
  • 簇的上界点为整个簇中深度最小的点。
  • Compress 树的中序遍历是按照深度降序遍历,从下到上。

Top Tree 是关于边的划分,而我们时常需要维护点的信息,为此我们定义:一个簇包含其连通块中的所有除上界点以外的点,这样每个点在每一层中最多属于一个簇。根节点的话可以加入一条额外边或特殊考虑来解决。

簇与 Top Tree 的性质

我们约定,如果没有特殊说明,“簇”都指的是 Top Tree 上的簇。下面列出一些比较显然但是重要的性质:

  • 根据定义,簇中只有不超过 2 个点与外部点邻接,这两个点一定是界点。(重要)
  • 任意两个簇,要么相互包含,要么两者的交最多只有一个点,且这个点是两个簇的共有的界点。
  • 一个不是基簇的簇,其两个子簇恰好有一个共有的界点,我们称其为该簇的中心点。
  • 从外部经过一个簇的路径必定经过该簇的界点。特别地,如果这个路径完全跨过了该簇,那么该簇的簇路径是其的一个子路径。

第一条的证明可以理解为,无论是 rake 还是 compress,都不影响“簇中只有不超过 2 个点与外部点邻接”这个性质。(结合第一张图理解)

Top Tree 是一棵二叉树,每个点是一个簇,父亲由儿子合并而来,叶子是一条条边的簇。

Top Tree 的结构在某种意义上类似于线段树:

  • 线段树的每个节点是一个区间,最多只有两端与外部邻接,而 Top Tree 的每个节点是一个簇,最多只有两端与外部邻接;(这也是他的信息可快速合并的原因之一)
  • 他们都维护子树信息并,是一种二叉分治结构,支持分治、二分,(某种意义上)是 Leafy 的;
  • 树高都是 \(O(\log n)\);
  • ……

所以我们可以将静态 Top Tree 直观理解为树上的线段树。

动态 Top Tree 的维护

就是支持一些动态操作的 Top Tree:

  • \(\text{expose}(u,v)\):将根簇变为簇路径为 \((u,v)\) 的一个簇。
  • \(\text{link}(u,v)\):在 \(u,v\) 间连边。
  • \(\text{cut}(u,v)\):断开 \(u,v\) 之间的边。

接下来我们将介绍 SATT(Self-Adjusting Top Tree)(的维护方式)。

Top Tree 与原树的关系

首先,我们已经发现 compress 操作与重链上 push_up 类似,rake 操作与轻子树信息合并类似。考虑到轻重链剖分与实链剖分的关系,为了能让 Top Tree 动起来,我们先把重链替换为实链、轻边替换为虚边。

假如有一棵树,我们令一个点为根、所有儿子指向父亲,再进行实链剖分,每条实链末端为一个叶子,即所有非叶节点都恰好有一个实儿子。

这时我们再对这棵树进行 Top Tree 的构建:对于一条实链上的每个点,我们先将其所有虚子树(分治地)rake 为一个簇,再对整个链上的边分治地 compress,分治过程中若一个中心点的虚子树还未被 rake 就先 rake 到路径上。

注意除了根所在的实链,其他实链需要 compress 的边还包含其链顶向上连的虚边!

image

下面举个例子:

image

解读一下:图中 rake tree 简写为了一个大写字母。最右图中,更直观地把 rake 过程画成了两个节点挨在一起的样子。注意到任意三个同层相邻节点 \(u,A,v\) 无论是 \(uA\) 挨在一起还是 \(Av\) 挨在一起都不影响最终的结果,这样最右图可以理解为一棵三叉树。

在实际代码中,我们也常常用三叉树的结构去储存我们的 Top Tree,其中在 compress tree 中一个点的中儿子一定是一棵 rake tree 的根,中儿子也决定了这个簇的中心点。

为了统一,我们令 rake tree 也是三叉的,只不过 rake node 一定没有中儿子而已。

下面对于刚刚所说进行一些更为广泛接受的定义 + 说明:

  • 指定一个度为一的点为根,并将所有边定向到它,这样的树称为 unit tree。
  • 将树分解为若干不交叉的、边不相交的路径(端点可以重合在另一条路径上)。根所在的路径称为 root path,或 exposed path。除了 root path 的其他路径需要满足开始于一个叶子、结束在另一个路径上。

我们的实链加上链顶的虚边,就相当于第二条中的“路径”。

  • 递归地表示整棵树的簇,我们把 root path 上每个点两侧的这些簇分别 rake 为 1 个,再将它尽可能晚地 rake 到路径上,即在这个点被 compress 前。
  • 我们用一个 augmented top tree 表示这个过程。表示某点某一侧 rake 的结点在 augmented top tree 中形成了一个树,称为 rake tree。每个表示 compress 的结点最多可能有 4 个孩子,其中 2 个表示两侧 rake 到路径上的簇,2 个表示这次被 compress 的簇,所以实际最多可能表示 2 次 rake 和 1 次 compress。同样这些结点也形成了一个树,称为 compress tree。如下图:

image

注意到他把簇 rake 成了两个,而我们 rake 成了一个。区别在于,他这样可以维护边的顺序,例如每个点的儿子有顺序,他就可以维护这个顺序。

那么他在分治地 compress 时若一个中心点的虚子树还未被 rake,就把他 rake 到路径上。

举个例子:上图左边 \(uv,A,B,vw\) 的合并,你可以选择 \(A\) 向 \(uv\) 合并、\(B\) 向 \(vw\) 合并,也可以选择 \(A\) 向 \(vw\) 合并、\(B\) 向 \(uv\) 合并。

这样,compress tree 可以类似地直观表示为一棵四叉树,两个儿子是 compress node、两个儿子是 rake node。

但是几乎没什么题要求你维护这个顺序,所以了解即可。

再附上一张图:

image

  • 对于原树上的一个点 \(u\),若 \(u\) 的度数 \(\ge 2\),记 \(N_u\) 表示在 \(u\) 处 compress 的簇;若 \(u\) 的度数 \(=1\),\(N_u\) 表示在 Top Tree 中最靠上的、一个界点为 \(u\) 的簇。
  • 仅考虑 compress tree 的话最后的结构类似 ST-tree(LCT),rake tree 可以认为是用于维护虚子树信息。

Top Tree 的动态操作

Rotate

注意到重链剖分、LCT 之间的关系,与静态 Top Tree、SATT 之间的关系比较类似:

  • LCT 本质上是把重链剖分的线段树变为可旋转、可加入删除一个 Splay 的 Splay(虚实转化),实现了动态化。
  • 那么 Top Tree 是否可以支持旋转、插入删除一个簇呢?

事实上,我们发现无论是 rake tree 还是 compress tree 都可以支持旋转。

以我们的三叉 compress tree 为例,由于只要不影响其中序遍历,怎么旋都可以,所以可以这样旋:

image

而 rake tree 是不关心 rake 的顺序的,并且 rake node 没有中儿子,所以像上图那样旋也是没问题的。

再说四叉 compress tree 的旋转,可以这样旋(图中右旋:\(\text{rotate}(N_v)\),左旋:\(\text{rotate}(N_w)\)):

image

(可以对着下面这幅图理解:)

image

而 rake tree 中每个点只有灰色儿子,同理也可以这样旋。

注意到每个点的 rake 儿子都不变,也就是他们的中心点是不变的。

实际有可能出现 \(u\) 是父亲的 rake 儿子等的情况,这些记得判掉。

Splay

有了 Rotate 就自然有了 Splay 了,这个东西与 LCT 的基本相同。

在 compress tree 中 \(\text{splay}(u)\) 可以把 \(u\) 的中心点变成整棵 compress tree 的根簇的中心点。

Splice

在讨论 Access 操作之前,我们先介绍一个操作 \(\text{splice}(u)\),表示将簇 \(u\) 插入到他父亲的簇上。这个就相当于刚刚说的“插入删除一个簇”的操作。

我们考虑三叉树时的情况,如下图:

image

其中 \(x,y,z,v\) 都是簇的界点,\(vy\) 表示以 \(v,y\) 为界点的一个簇。

设 \(u\) 为 \(A,B\) 在 rake tree 上的根的父亲。

首先 \(\text{splay}(u)\) 可以使点 \(v\) 成为这个父亲的簇的中心点。

然后观察发生了什么:

image

图中把 rake tree 展开了一下。

发现只要 swap(xv, yv) 再更新一下信息即可!

而对于四叉树,他还需要维护儿子顺序,如下图:

image

原来只有簇 \(A\) 在上方、簇 \(B,vy,C\) 在下方,现在变成了簇 \(A,xv,B\) 在上方、\(C\) 在下方。

发生了什么:

image

仔细看的话这个图应该比较好懂,按图中这样修改就行了。


接下来只讲三叉树怎么操作的了,四叉树可以参考原论文第 5 ~ 6 页 \(\mathbf{Exposing\ the\ target.}\) 开始的一堆(实在啃不动了

我们先对动态的 Top Tree 放宽一点要求,不要求每条实链末端为叶子了,不然讨论起来比较麻烦。

Access

\(\text{access}(x)\) 表示让点 \(x\) 与原树的根节点组成根簇的界点。

先讲过程:

记 \(x'=N_x\)。\(N_u\) 的定义在上文“Top Tree 与原树的关系”倒数第二条处。

  • 若 \(x\) 不是他所在的 compress tree 的根节点的一个界点,则先 \(\text{splay}(x')\),再新建点 \(y\),令 \(x'\) 的左儿子、中儿子分别成为 \(y\) 的左儿子、右儿子,并删除 \(x'\) 的左儿子、中儿子,然后令 \(y\) 为 \(x'\) 的中儿子。
  • 重复以下操作,直到 \(x'\) 到达根部(compress tree 的根节点为整棵 SATT 的根节点):
    • 记此时 \(x'\) 的父亲为 \(u\),执行 \(\text{splice}(x')\).
    • 若 \(u\) 是 rake node 且 \(u\) 的儿子数量小于 2,删除 \(u\)、将 \(u\) 的儿子继承到 \(u\) 的父亲。
    • \(\text{splay}(x')\) 到当前 compress tree 的根的儿子。
    • \(x'\gets\text{fa}(x')\)。
  • 将 \(x'\) 旋转至整棵树的根。

第一步将 \(x\) 成为其所在 compress tree 对应簇的界点,称为 Local Splay。如下图:

image

然后如图:

image

这样一直重复。最后一步称为 Global Splay。

Makeroot

显然,先 \(\text{access}(x)\) 再给 \(x\) 子树打上翻转标记即可。标记下传讨论下 compress node 与 rake node 即可。

Expose

\(\text{expose}(u,v)\) 将根簇变为簇路径为 \((u,v)\) 的一个簇。

显然,先 \(\text{makeroot}(u)\) 再 \(\text{access}(v)\) 即可。

\(\text{link}(x,y,z)\) 表示连边 \(x,y\),这条边在 SATT 上编号为 \(z\)。

先 \(\text{access}(x)\),再 \(\text{makeroot}(y)\),再让 \(x\) 的左儿子为 \(z\)、\(z\) 的中儿子为 \(y\) 即可。

image

Cut

先 \(\text{expose}(x,y)\),再删除 \(x\) 的右儿子,最后断开 \(x,y\) 即可。

图示:

image


于是 SATT 的基本操作就讲完了。

时间复杂度证明:不会。

SATT 维护其他信息主要都是修改 \(\text{push_up}\) 与 \(\text{push_down}\) 函数,或者通过 DFS(或者说:非局部搜索)来求,这些维护方式放到静态 Top Tree 上是完全一样的,故放到静态 Top Tree 中来讲。

简单萌萌哒 Top Tree(下)


整篇文章还不保熟,代码还没写,有错误请包涵并指出。

我也是翻阅、参考、引用抄写一大堆资料才能呈现出这篇文章,以下是一些可能有用的链接:

标签:Tree,萌萌,rake,compress,tree,text,Top
From: https://www.cnblogs.com/laijinyi/p/18373391/Top-Tree-1

相关文章

  • Lanelet2与OpenDrive和OpenStreetMap的关系
    Lanelet2、OpenDrive和OpenStreetMap在自动驾驶和智能交通系统中都扮演着重要角色,但它们之间在功能和用途上存在一些差异。以下是它们之间关系的详细阐述:Lanelet2定义与功能:Lanelet2是一个专为自动驾驶和智能交通系统设计的高精度道路网络表示框架。它提供了丰富的数据结......
  • 重磅发布:最新AI应用榜全球Top100!你用过哪几款?
    8月22日,硅谷知名风投A16Z(AndreessenHorowitz),发布了最新(第3期)的全球Top100生成式AI消费者应用榜单,涵盖了网页端AI应用Top50和移动端AI应用Top50。网页端Top50移动端Top50通过对比半年前的榜单,有几个值得关注的亮点:1.与半年前相比,榜单中有30%的新面孔,显示出AI应用的......
  • 2024年了,你还在手动打字?Top4懒人技巧,让你秒变高效达人!
    在忙碌的现代工作环境里,我们经常需要处理大量的信息,比如会议记录、客户谈话或者远程合作时的录音。录音是个好东西,因为它能帮我们记下所有重要的细节。但问题来了,这么多录音文件,怎么才能快速把它们变成文字呢?这可是让很多人头疼的大事。不过别担心,2024年出现了几款超给力的录音......
  • 【北京迅为】itop-龙芯2k1000 sylixos 嵌入式实时系统烧写手册-第一章与第二章 详细步
      第一章准备与说明1.1文档说明l该文档适用于龙芯2K1000开发板;l用于实现无根文件系统的SylixOS硬盘固化自启动;l包含根文件系统的导出说明。1.2准备工作l1台有以太网口的电脑,1条网线、1条串口线;lTFTP功能:电脑需要安装“RealEvo-IDE”或者“Tftp32”软件......
  • Linux磁盘监控管理(fdisk\df\du和iotop、iostat)
    1.fdisk\df\du基本语句及其含义fdiskfdisk-l:表示列出系统中所有可识别的硬盘、U盘等设备的分区情况。此外还有其他参数:p:列出分区表。这是最常用的命令之一,用于查看当前磁盘的分区情况。d:删除分区。用于删除现有的磁盘分区。n:创建新分区。用于在磁盘上创建新的分区。t:改......
  • [HTML 5] Autoplay video
    Normalbrowserhasrestictionthatdoesn'tallowyouautomaticllyplaythevideo,unlessyoursitehashighMediaEnagementindex(MEI).Thereare2waytoresolvethisproblem.1.ClickandPlayasyncfunctionplay(){try{awaitvdo.pl......
  • WPF LogicalTree vs Visual Tree
    Copyfrom https://www.c-sharpcorner.com/blogs/wpf-logical-and-visual-trees1  WPF'shierarchicalstructurerequiresanewconceptualmodelofapplicationstructure,whichtakestheformofanelementtree.Twotypesofelementtreesarerequiredt......
  • Minimum Steiner Tree 题解
    原题,详见P10723。几乎相同,我们只需要以一个需要选择的点为根,遍历子树看看有没有出现需要选择的点,然后直接去删除即可,可以看我的博客。但是我们也可以换一种做法,用类似拓扑排序的算法。先找到所有只连一条边且没有被选择的点,然后放进队列,接着依次取出队头遍历与它相连的点,同时记......
  • 树上启发式合并——dsu on tree
    参考文章:树上启发式合并[dsuontree]树上启发式合并总结树上启发式合并の详解启发式合并启发式算法是什么呢?启发式算法是基于人类的经验和直观感觉,对一些算法的优化。举个例子,最常见的就是并查集的启发式合并了,代码是这样的:voidmerge(intx,inty){intxx=find(x......
  • ABC 368D Minimum Steiner Tree
    题意给你一颗由N个点组成的树,指定K个节点,求包含这K个节点的最小子树的大小思路考虑正难则反,我们从开始的树当中剪掉那些没有任何指定点的子树,剩下来的子树就是最小的、能包含所有指定节点的子树。关于剪去这个操作,就是dfs一旦遇到以当前节点为根的子树没有任何指定点时,就停止df......