Lowest Common Ancestor
Definition
The Lowest Common Ancestor (LCA) of two nodes is the deepest node that is an ancestor to both.
For convenience, we denote the LCA of a node set \(S=\{v_1,v_2,\ldots,v_n\}\) as \(\text{LCA}(v_1,v_2,\ldots,v_n)\) or \(\text{LCA}(S)\).
Properties
This section is translated and adapted from wcipeg.
- \(\text{LCA}(\{u\})=u\);
- \(u\) is an ancestor of \(v\) if and only if \(\text{LCA}(u,v)=u\);
- If \(u\) is not an ancestor of \(v\) and \(v\) is not an ancestor of \(u\), then \(u\) and \(v\) lie in different subtrees of \(\text{LCA}(u,v)\);
- In preorder traversal, \(\text{LCA}(S)\) appears before all elements of \(S\), and in postorder traversal, \(\text{LCA}(S)\) appears after all elements of \(S\);
- The LCA of the union of two sets is the LCA of the LCAs of each set, i.e., \(\text{LCA}(A\cup B)=\text{LCA}(\text{LCA}(A), \text{LCA}(B))\);
- The LCA of two nodes must lie on the shortest path between them on the tree;
- \(d(u,v)=h(u)+h(v)-2h(\text{LCA}(u,v))\), where \(d\) is the distance between two nodes on the tree, and \(h\) represents the distance from a node to the root.
Algorithms
Naive Algorithm
Process
Each time, take the node with greater depth and let it jump upward. Clearly, on the tree, these two points will eventually meet, and the meeting point is the desired LCA.
Alternatively, first adjust the deeper node upward until both have the same depth, then jump upward together. They will also eventually meet.
Properties
The naive algorithm requires a DFS of the entire tree for preprocessing, with time complexity \(O(n)\). A single query takes \(\Theta(n)\) time. If the tree has random properties, the time complexity relates to the expected height of such a random tree.
Binary Lifting
Process
Binary lifting is the classic LCA algorithm, an improvement over the naive algorithm. By preprocessing the \(\text{fa}_{x,i}\) array, the cursor can move quickly, significantly reducing the number of jumps. \(\text{fa}_{x,i}\) denotes the \(2^i\)-th ancestor of node \(x\). The \(\text{fa}_{x,i}\) array can be preprocessed via DFS.
Now, let's see how to optimize these jumps:
In the first phase of adjusting the cursors, we need to bring \(u\) and \(v\) to the same depth. We can compute the depth difference \(y\) between \(u\) and \(v\). By binary splitting \(y\), we optimize \(y\) jumps to the number of 1s in the binary representation of \(y\).
In the second phase, we try from the largest \(i\) down to \(0\) (inclusive). If \(\text{fa}_{u,i}\not=\text{fa}_{v,i}\), then set \(u\gets\text{fa}_{u,i},v\gets\text{fa}_{v,i}\). The final LCA is \(\text{fa}_{u,0}\).
Properties
The preprocessing time complexity of binary lifting is \(O(n \log n)\), and a single query takes \(O(\log n)\).
Additionally, swapping the two dimensions of the fa array to place the smaller dimension first can reduce cache misses and improve efficiency.
Example Problem
HDU 2586 How far away? Query the shortest path on a tree.
One can first compute the LCA and then use property \(7\) to answer. Alternatively, compute the result directly while finding the LCA.
Reference 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 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 | |
Tarjan's Algorithm
Process
Tarjan's algorithm is an offline algorithm that uses a Disjoint Set Union (DSU) to record the ancestor of a node. The procedure is as follows:
- First, accept input edges (adjacency list) and query edges (stored in another adjacency list). Query edges are virtually added; for convenience, when inputting a query edge, add both the edge and its reverse to the
queryEdgearray. - Then perform a DFS traversal, using a
visitedarray to record whether a node has been visited, andparentto record the current node's parent. - It involves the backtracking idea: when we traverse to a node, we consider its root to be itself. After the DFS rooted at this node is complete, set the node's root to its parent.
- During backtracking, if for a node as the start, the other node of a
queryEdgequery edge has also been visited, directly update the LCA result for that query edge. - Finally, output the results.
Properties
Tarjan's algorithm requires initializing the DSU, so preprocessing takes \(O(n)\) time.
The naive Tarjan's algorithm processes all \(m\) queries in \(O(m \alpha(m+n, n) + n)\) time, but its constant factor is larger than binary lifting. There exists an \(O(m + n)\) implementation.
Note
The statement "the DSU used in the naive Tarjan LCA algorithm has special properties, making a single call to find() amortized \(O(1)\)" is incorrect.
The naive Tarjan implementation below has complexity \(O(m \alpha(m+n, n) + n)\). For a strictly linear algorithm, refer to the 1983 paper by Gabow and Tarjan, which provides an \(O(m + n)\) approach.
Implementation
Reference 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 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 | |
Reduction to RMQ via Euler Tour Sequence
Definition
Perform a DFS on the tree. Record the node's identifier every time we visit it (whether first visit or backtrack). This yields a sequence of length \(2n-1\), called the Euler tour sequence of the tree.
Below, let \(pos(u)\) denote the position where node \(u\) first appears in the Euler sequence (also called the Euler order of node \(u\)), and denote the Euler sequence itself as \(E[1..2n-1]\).
Process
With the Euler sequence, the LCA problem can be reduced to an RMQ problem in linear time: \(pos(LCA(u, v))=\min\{pos(k)|k\in E[pos(u)..pos(v)]\}\).
This equality is intuitive: the path from \(u\) to \(v\) must pass through \(LCA(u,v)\) but not through ancestors of \(LCA(u,v)\). Therefore, the node with the smallest Euler order on the path from \(u\) to \(v\) is \(LCA(u, v)\).
Computing the Euler sequence via DFS takes \(O(n)\) time, and the Euler sequence length is also \(O(n)\). Thus, the LCA problem can be reduced to an RMQ problem of the same scale in \(O(n)\) time.
Implementation
Reference 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 33 34 | |
When we need to query the LCA of a pair \((u, v)\), query the node representing the minimum value in the interval \([\min\{pos[u], pos[v]\}, \max\{pos[u], pos[v]\}]\).
If using a Sparse Table (ST) to solve the RMQ problem, this algorithm does not support online modifications. Preprocessing takes \(O(n\log n)\) time, and each LCA query takes \(O(1)\) time.
Heavy-Light Decomposition
The LCA is the shallower of the two cursors when they jump onto the same heavy chain.
Heavy-light decomposition preprocessing takes \(O(n)\) time, and a single query takes \(O(\log n)\) time with a small constant factor.
Link Cut Tree
In a Link Cut Tree, suppose the points of two consecutive access operations are u and v. Then the point returned by the second access operation is the LCA of u and v.
Without link and cut operations, a single query using Link Cut Tree takes \(O(\log n)\) time.
Standard RMQ
As mentioned earlier, using the Euler order reduces LCA to RMQ, with the bottleneck being RMQ. If we can solve RMQ in \(O(n) \sim O(1)\), then we can solve LCA in \(O(n) \sim O(1)\).
Note that the Euler sequence satisfies that adjacent numbers differ by 1 or -1, so we can use \(O(n) \sim O(1)\) ±1 RMQ.
Time complexity \(O(n) \sim O(1)\), space complexity \(O(n)\), supports online queries, but has a large constant factor.
Example Luogu P3379【Template】Lowest Common Ancestor (LCA)
Reference 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 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 | |
Exercises
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用