Top Tree
Self-Adjusting Top Tree
Introduction
Self-Adjusting Top Tree is a data structure proposed by Tarjan and Werneck in their 2005 paper Self-Adjusting Top Trees, based on Top Tree theory, for maintaining fully dynamic forests. It is abbreviated as SATT.
Self-Adjusting Top Tree can perform operations such as path modification/query, subtree modification/query, and non-local search on any tree in the forest.
Splay Tree is the foundation of SATT, but the Splay Tree used in SATT differs in details from the ordinary Splay (some extensions have been made).
Problem Introduction
Maintain a forest, supporting the following operations:
- Delete or add an edge, ensuring the forest remains a forest after the operation.
- Modify the weights of a simple path in a tree.
- Modify the weights of a subtree rooted at a node.
- Query the sum of weights of a simple path in a tree.
- Query the sum of weights of a subtree rooted at a node.
Tree Contraction
For any tree, we can apply tree contraction theory to contract it into a single edge.
Specifically, tree contraction has two basic operations: Compress and Rake. The Compress operation specifies a node \(x\) with degree \(2\), with its two adjacent nodes denoted as \(y\) and \(z\). We add a new edge \(yz\); store the information of node \(x\), edge \(xz\), and edge \(xy\) in \(yz\), and then delete them. As shown in the figure.
The Rake operation specifies a node \(x\) with degree \(1\), and the adjacent node \(y\) must have degree greater than \(1\). Let another neighbor of \(y\) be \(z\). We store the information of node \(x\) and edge \(xy\) into edge \(yz\), and then delete them. As shown in the figure.
It can be proven that any tree can be contracted into a single edge using only Compress and Rake operations, as shown in the figure.
Cluster
For convenience, denote the original tree before any operations as \(T\). The tree after performing some tree contraction operations (possibly none) on \(T\) is denoted as \(T_x\).
We examine the information contained in a certain edge in some \(T_x\).
Besides its own information (if this edge does not exist in \(T\), it has no intrinsic information), this edge may also contain information from other points and edges merged onto it via Compress/Rake operations. Let's first select an edge from the tree contraction process in the figure below and see which points and edges in \(T\) its information represents.
As shown, the selected edge and its corresponding subgraph are circled in red.
It can be seen that the points and edges represented by the information of this edge in \(T\) are connected. We can infer that for any edge in any \(T_x\), the stored information always corresponds to a connected subgraph in \(T\). We call such a connected subgraph a Cluster.
However, a cluster is an incomplete subgraph; it contains some edges whose endpoints are not included in the cluster itself. We call these endpoints the Endpoints of the cluster, the points of the connected subgraph it contains Internal Nodes, and the edges of the connected subgraph Internal Edges.
For any cluster, the following properties hold:
- A cluster only stores and maintains information about internal nodes and internal edges.
- A cluster has two endpoints. These two endpoints are the two points connected by the edge representing the cluster in \(T_x\). The path between the two endpoints is called the Cluster Path; denote the two endpoints of a cluster as \(x\) and \(y\), we use \(C(x,y)\) to represent this cluster.
- Internal nodes are only connected to endpoints or other internal nodes.
In particular, each edge in \(T\) independently forms a cluster (containing only its own information), which we call a Base Cluster. For the final \(T_x\) contracted to a single edge, the edge represents a cluster containing all information of \(T\) except the two endpoints. This cluster is called the Root Cluster.
As shown, the base clusters mentioned above are marked with red lines.
From the perspective of clusters, Compress/Rake operations "merge" two clusters into one, leaving a new cluster. Thus, the tree contraction process is also the process of merging all base clusters into one cluster.
Therefore, we can also obtain the following figure, which is another representation of a series of tree contraction operations.
Top Tree
We now want to represent the entire tree contraction process for a given tree.
We can use the two methods mentioned above to represent this process, but this is cumbersome. If tree contraction takes \(n\) steps, we need \(n\) trees to represent the entire contraction.
Considering a more concise representation of a tree contraction for a given tree, we introduce the Top Tree.

As shown, this is a Top Tree based on the contraction method and original tree described above.
Top Tree has the following properties:
- A Top Tree corresponds to an original tree and a method of contracting it. Each node of the Top Tree represents an edge in some \(T_x\), i.e., a cluster formed during the tree contraction process. Points like \(N_x\) in the figure represent the cluster formed by the operation
compress(x). - A node in the Top Tree has two children (each representing a cluster). The cluster represented by this node is the new cluster obtained by merging the two child clusters via Compress or Rake operations.
- The leaf nodes of the Top Tree are base clusters, and its root node is the root cluster. Therefore, if we layer the Top Tree by its topological order, each layer represents a \(T_x\).
Information Maintenance Using Ternary Self-Adjusting Top Tree
Principle
The great simplification of the tree contraction process by Top Tree shows the possibility of maintaining tree information by maintaining the contraction process. SATT uses this principle to maintain tree information.
Note that the tree contraction process is also the process of gradually incorporating tree information. When we execute a compress(x), the information of point \(x\) starts to appear in some cluster from that moment, affecting our statistical results.
Suppose we now use a Top Tree to maintain a tree \(T\), where each point and edge has a weight, and we want to maintain the sum of weights of \(T\).
If we now modify the weight of a point \(x\) in \(T\) during maintenance, we obviously need to change the node information in the Top Tree for all clusters that include \(x\). Doing so would result in a single-operation time complexity of \(O(n)\).
However, if we choose a point such that the number of nodes in the Top Tree whose cluster information includes \(x\) is small—meaning its information is added to clusters as late as possible—the single-operation time complexity can be greatly improved. As shown in the figure.

SATT maintains tree information by modifying the order in which information of a certain point/path is added to clusters during tree contraction (to reduce the single-operation time complexity when modified).
Actual Structure
First, we root the original tree \(T\) and assign layers. Then consider the root cluster of the Top Tree for a certain tree contraction order. It has two endpoints; we let one endpoint be the root of the original tree, and the other endpoint is arbitrarily chosen.

As shown, select a pair of endpoints for the root cluster; here endpoints are also circled when marking clusters.
From the basic operations of tree contraction, the information of points and edges on the cluster path \((j,h,c,jh,hc)\) is finally added to \(C(k,g)\) via Compress operations, while non-cluster-path points \((a,b,i,f,g,e,ig,\cdots)\) are added to \(C(k,g)\) via Rake operations.
We extract the cluster path separately; this is a tree with a special shape (a chain). We build a top tree for this tree (its represented tree contraction order is arbitrary).

We call this structure Compress Tree, because in this Top Tree, any node's two children are merged into their parent via Compress operations.
Nodes in the Compress Tree are called Compress Nodes. Considering only the current cluster path, a non-leaf Compress Node represents a compress process, meaning merging the information of the left and right children, then adding the information of point \(x\) stored by compress(x) itself. This Compress Tree maintains the information of the cluster path of \(C(k,g)\).
Additionally, in the Compress Tree, we actually impose some restrictions on the Top Tree used. Note that the Compress Tree maintains a chain in \(T\) where the depths of points are all different. We stipulate that in the Compress Tree, the in-order traversal order of base clusters matches the depth order of the corresponding edges in \(T\), with smaller in-order traversal corresponding to shallower depth. Similarly, the same applies to the relationship for each point \(x\) corresponding to compress(x).
Now to maintain the information of those non-cluster paths, assume that points and edges on these non-cluster paths have already formed maximal clusters, and these maximal clusters are formed by Rake operations among smaller clusters circled in blue. For the process of merging several smaller clusters into a maximal cluster, we represent it with a ternary tree. Similarly, we call this structure Rake Tree, and correspondingly, points in the Rake Tree are Rake Nodes. Each Rake Node represents a cluster, formed by Raking its left and right children onto the smaller cluster represented by its middle child. See the figure below for details; note that each point in the Rake Tree represents a smaller cluster in \(T\) with the same endpoints.

As shown, blue circles indicate maximal clusters, and yellow circles indicate smaller clusters.
For those smaller clusters, we process them similarly: select cluster paths, build Compress Trees, ... Recursively, we build many Compress Trees and Rake Trees representing tree contraction processes.

The above figure shows the Rake-Compress Tree of the original tree (since each Rake Node is connected to a Compress Tree, it appears as a Rake Tree connected to many Compress Trees) and the Compress Tree representing the root cluster path.
Consider concatenating these trees in some way to form an ordered whole. Denote the common endpoint of the set of minimal clusters represented by a Rake Tree as point \(x\). We add the other endpoint (not \(x\)) to the middle children of these Rake Nodes (a set of Compress Trees), while still maintaining their in-order traversal and basic properties of the Top Tree, as shown.

This step essentially allows the Rake operation that adds a point in \(T\) to occur directly in the Compress Tree. This not only enables correct maintenance of Rake Node information (just merge the three children's information) but also makes the Compress Tree structure more complete. Next, we modify the Compress Tree to a ternary tree. If the common endpoint of a Rake Tree is point \(x\), we attach the Rake Tree to the middle child of compress(x), as shown.

Now, after ternary transformation, the point compress(x) means: first Rake its middle child onto the cluster path, then combine the information of the left and right children and point \(x\).
Finally, we process the Compress Tree of the root cluster path: consistently with all other Compress Trees, add its two endpoints according to in-order traversal, so that its root stores the entire \(T\) information.
Thus, we achieve information maintenance of a tree using a ternary Self-Adjusting Top Tree.

To summarize, SATT has the following properties:
- SATT consists of Compress Trees and Rake Trees. A Compress Tree is a special Top Tree; a Rake Tree is a ternary tree. Both correspond to the tree contraction process of a tree.
- A point in a Compress Tree has at most three children. Compress Trees can perform rotation operations similar to Splay Trees (just ensure the in-order traversal remains unchanged; when rotating a point, keep its middle child fixed).
- A point in a Rake Tree always has a middle child. Rake Trees can perform rotation operations similar to Splay Trees (just ensure the in-order traversal remains unchanged; when rotating a point, keep its middle child fixed).
- The topological order of SATT reflects the tree contraction order of the original tree \(T\).
Can SATT achieve "modifying the order in which information of a certain point/path is added to clusters during tree contraction" mentioned above? The answer is yes.
In SATT, there is an access(x) operation, whose purpose is to make point \(x\) become the non-root endpoint of the root cluster, while making compress(x) become the root of SATT.
We can use the access(x) operation with amortized \(O(\log n)\) complexity to rotate the point representing compress(x) in SATT to the root of the entire SATT. According to the fourth property of SATT, we change the operation order of compress(x), making it execute last, so the information of point \(x\) is added last. Thus, when we want to modify the information of point \(x\), we only need to update compress(x).
Code Implementation
Push Functions
First consider pushing up information, i.e., the Pushup(x) function. When maintaining information for a node in SATT, first discuss whether this node is in a Compress Tree or a Rake Tree, as explained above. Below we take maintaining the subtree size of a node as an example.
1 2 3 4 5 6 7 8 9 10 11 12 | |
To query the subtree size of point \(x\), Access it to the SATT root; the answer is the size of its middle child \(+1\); because according to the above, after Access, its middle child is its true subtree.
Then consider pushing down information, i.e., the Pushdown(x) function. If we want to modify an entire subtree in the original tree, a natural idea is: Access the node to the SATT root, and mark its middle child. Similarly, query the subtree by Accessing and then querying the middle child.
If we want to modify an entire path in the original tree, we expose the two endpoints of the path, where expose(x, y) means making point \(x\) the root of \(T\), and point \(y\) the other endpoint of the root cluster. Correspondingly in SATT, the Compress Tree of the root cluster is the path from \(x\) to \(y\). Then directly mark the Compress Tree of the root cluster. Similarly, query the chain by exposing and then querying the root node.
Thus we know how to solve the problem introduced.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Splay Functions
We know that Rake Trees and Compress Trees in SATT can be rotated, meaning they can be maintained with Splay. Therefore, we can write the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |
Note that the functions direction and isroot differ from ordinary Splay. Because no matter how this point rotates, its middle child does not change.
Access Functions
access(x) means: rotate point \(x\) to the root of the entire SATT, making point \(x\) one of the two endpoints of the root cluster (the other endpoint is the root of \(T\)), without changing the structure of the original tree and the root of the original tree.
To implement access(x), we first rotate it to the root of its Compress Tree, then remove the right child of point \(x\), making point \(x\) an endpoint of the cluster corresponding to its Compress Tree.
1 2 3 4 5 6 7 8 9 | |
If point \(x\) has reached the root, exit; if not, perform the following steps to let it cross the Rake Tree above it:
- Splay its parent node (must be a Rake Node) to the root of its Rake Tree.
- Splay \(x\)'s grandparent node (must be a Compress Node) to the root of its Compress Tree.
- If the grandparent node has a right child, swap point \(x\) with the grandparent's right child, update information, then exit.
- If the grandparent node has no right child, first make point \(x\) the right child of the grandparent. At this point, \(x\)'s original parent node has no middle child; according to the properties of Rake Node, it cannot exist. So call the
Deletefunction to delete it, then exit.
Steps 1 and 2 together are called Local Splay. Steps 3 and 4 together are called Splice. For convenience, we write them both in the Splice(x) function.
The Delete(x) function mentioned above is as follows:
- Check if the point \(x\) to be deleted has a left child. If so, rotate the left child's subtree to below point \(x\) (becoming the new left child), then make the right child (if any) the right child of the left child. Now the left child replaces point \(x\). This is equivalent to a merge operation in Splay.
- If there is no left child, directly let its right child replace point \(x\).
It is not hard to see that Splice(x) changes the endpoint selection of some clusters in the original tree. After one splice, we treat point \(x\)'s parent as the new point \(x\) and perform the next splice.
Eventually, we find that the point \(x\) we initially operated on must be at the far right of the Compress Tree of the root cluster. We just need to perform a final Global Splay to rotate it to the SATT root.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | |
To make a point the root of the original tree, we Access point \(x\) to the root of SATT. At this point, \(x\) is already an endpoint of the final cluster. By the in-order traversal property of Compress Tree, swapping left and right children (swap left and right sons of all points) of the Compress Tree where point \(x\) is located makes point \(x\) the root of the original tree. In implementation, we achieve this by marking point \(x\) with a reversal tag and later pushing it down.
1 2 3 4 | |
Thus expose(x, y) is straightforward:
1 2 3 4 | |
Link & Cut
Now we want to add an edge between two disconnected points in the original tree. First, make one point \(x\) the root of the original tree, then rotate the other point \(y\) to the root. At this point, point \(y\) should become the right child of point \(x\). Then attach this edge to the right child of point \(y\) (in SATT that only maintains points, this step can be omitted).
1 2 3 4 5 6 7 8 9 | |
Cut is similar in principle to Link.
1 2 3 4 5 6 | |
Complete Code
Luogu P3690【Template】Dynamic Tree
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | |
Time Complexity Proof of SATT
Let the potential function for the current state \(x\) of a SATT (with \(n\) nodes) be
where \(r(i) = \lceil \log_2 \text{siz}(i) \rceil\). \(\text{siz}(i)\) is the size of the subtree rooted at \(i\).
Then the amortized complexity of splay in SATT is clearly still \(3n\log n + 1\), even though SATT is a ternary tree.
Therefore, for SATT, as long as we prove the complexity of the Access function is correct, we prove the time complexity of SATT.
We analyze the amortized complexity of Access step by step.
First, we rotate point \(x\) to the root of its Compress Tree. The amortized complexity of this step is
Next, we make point \(x\) have no right child. The amortized complexity of this step is

As shown, the process of removing the right child of point \(x\).
Then the process of alternating Local Splay and Splice. After several Splices, point \(x\) is rotated to the root of SATT. We analyze one set of Local Splay and Splice:



As shown, illustrates the process of performing one Splice on point \(x\), excluding the final left rotation of point \(x\).
For convenience, let \(r_x(i)\) be the \(r\) value of point \(i\) in state \(x\).
From the figure, it is easy to see that the amortized complexity of the operation from state 1 to state 2 (Local Splay rotating point \(x\)'s parent to the root of its Rake Tree) is
From the figure, the amortized complexity of the operation from state 2 to state 3 (Local Splay rotating point \(x\)'s grandparent to the root of its Compress Tree) is
Focus on the operation from state 3 to state 4 (Splice)
It is not hard to see \(r_4(\gamma) \leq r_3(B)\)
Thus the amortized complexity of this operation is
Combining the above, the complexity of one Splice is
Denote the \(r\) value of the next Splice point \(X\) (point \(B\) in state 4) as \(r'(X)\), and note that \(r_3(\gamma),r_1(\gamma) \ge r_1(X)\), \(r_3(B),r_2(\gamma) \leq r'(X)\) and \(r_3(B)=r_2(B)\), so
Besides this complexity, there may be additional amortized complexity due to delete(x) in Splice, denote this part as \(a' \leq 3\log n +1\).
Ignoring \(a'\) for now, each Splice's \(r'(X)\) equals the next \(r(X)\), and the first Splice's \(r(X)\) equals the \(r(X)\) when we initially rotate point \(x\) to its Compress Tree root. Then for an access(x) without counting delete(x), we have:
where \(k\) is the number of Splices.
It seems \(a\) will have a \(3k+1\) term making amortized complexity hard to analyze, but we have a way to handle it. Note that zig-zig/zig-zag rotations can be amortized as
If we can find enough zig-zig, zig-zag operations, we can spread this \(3k+1\) over these operations, thus eliminating the \(3k+1\).
We find that Global Splay has many such zig-zig, zag-zig operations for us to use, because the number of points in Global Splay must be greater than \(k\), and the number of points on the path from point \(x\) to the root of Global Splay is at least \(k\). That means in one access(x), there will be at least \(\dfrac k2\) zig-zag operations. Including the amortized complexity of Global Splay \(a \leq 3\log n +1\), the amortized complexity of one access(x) without counting delete(x) is
Now including \(a'\), write the total expression for \(m\) access(x) operations.
We want the actual complexity
Note that the delete(x) operation essentially deletes a Rake Node, but in \(m\) operations we add at most \(m\) Rake Nodes. By the definition of Rake Node, initially we have at most \(n\) Rake Nodes, meaning we will perform at most \(m+n\) delete(x) operations. Since \(a' \leq 3\log n +1\), we have
Thus we have proven the complexity of Access, and since other functions are either based on Access or have constant time complexity, we have proven the complexity of SATT.
Incidentally, if we omit the Global Splay process like LCT, and instead directly rotate the point to be Accessed during each Splice, the time complexity is also correct (in practice, the version omitting Global Splay is much faster, comparable to LCT on Luogu P3690).
Example Problems
Example 1
CEOI 2019 Dynamic Diameter
Given a tree with \(n\) nodes, each edge has a weight. There are \(q\) updates, each modifying the weight of an edge, and querying the diameter of the tree. Forced online.
Maintain dynamic diameter. After building SATT, we only need to maintain the answer for each point in Pushup(x), and finally query the answer of the root node (i.e., the diameter of the entire tree).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
Here \(diam\) is the answer of the current point (the diameter of the cluster represented by this point). \(len\) represents the length of the cluster path of the current Compress Node, \(maxs_{0/1}\) represents the maximum distance from the Compress Node to points and endpoints inside the cluster without choosing the cluster path child/without choosing the parent (if it's a Rake Node, only store \(maxs_0\), the maximum distance from the upper endpoint of the current cluster to points and endpoints inside the cluster). Each query asks for the diam of the SATT root node; correctness is obvious.
Note to modify Pushrev(x) accordingly.
```cpp void pushrev(int x) { if (!x) return; r[x] ^= 1; swap(ls(x), rs(x)); swap(maxs[x][0], maxs
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用