Table of Contents
1. Lowest Common Ancestor – O(√N)O(√N) and O(logN)O(logN) with O(N)O(N) preprocessing
Given a tree GG. Given queries of the form (v1,v2)(v1,v2), for each query you need to find the lowest common ancestor (or least common ancestor), i.e. a vertex vv that lies on the path from the root to v1v1 and the path from the root to v2v2, and the vertex should be the lowest. In other words, the desired vertex vv is the most bottom ancestor of v1v1 and v2v2. It is obvious that their lowest common ancestor lies on a shortest path from v1v1 and v2v2. Also, if v1v1 is the ancestor of v2v2, v1v1 is their lowest common ancestor.
2. The Idea of the Algorithm
Before answering the queries, we need to preprocess the tree. We make a DFS traversal starting at the root and we build a list eulereuler which stores the order of the vertices that we visit (a vertex is added to the list when we first visit it, and after the return of the DFS traversals to its children). This is also called an Euler tour of the tree. It is clear that the size of this list will be O(N)O(N). We also need to build an array first[0..N−1]first[0..N−1] which stores for each vertex ii its first occurrence in eulereuler. That is, the first position in eulereuler such that euler[first[i]]=ieuler[first[i]]=i. Also by using the DFS we can find the height of each node (distance from root to it) and store it in the array height[0..N−1]height[0..N−1].
So how can we answer queries using the Euler tour and the additional two arrays? Suppose the query is a pair of v1v1 and v2v2. Consider the vertices that we visit in the Euler tour between the first visit of v1v1 and the first visit of v2v2. It is easy to see, that the LCA(v1,v2)LCA(v1,v2) is the vertex with the lowest height on this path. We already noticed, that the LCA has to be part of the shortest path between v1v1 and v2v2. Clearly it also has to be the vertex with the smallest height. And in the Euler tour we essentially use the shortest path, except that we additionally visit all subtrees that we find on the path. But all vertices in these subtrees are lower in the tree than the LCA and therefore have a larger height. So the LCA(v1,v2)LCA(v1,v2) can be uniquely determined by finding the vertex with the smallest height in the Euler tour between first(v1)first(v1) and first(v2)first(v2).
Let’s illustrate this idea. Consider the following graph and the Euler tour with the corresponding heights:
Vertices:1252621314741Heights:1232321212321
The tour starting at vertex 6 and ending at 4 we visit the vertices [6,2,1,3,1,4]. Among those vertices the vertex 1 has the lowest height, therefore LCA(6, 4) = 1.
To recap: to answer a query we just need to find the vertex with smallest height in the array euler in the range from first[v1] to first[v2]. Thus, the LCA problem is reduced to the RMQ problem (finding the minimum in an range problem).
Using Sqrt-Decomposition, it is possible to obtain a solution answering each query in O(√N) with preprocessing in O(N) time.
Using a Segment Tree you can answer each query in O(logN) with preprocessing in O(N) time.
Since there will almost never be any update to the stored values, a Sparse Table might be a better choice, allowing O(1) query answering with O(NlogN) build time.
3. Implementation
In the following implementation of the LCA algorithm a Segment Tree is used.
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 | struct LCA { vector< int > height, euler, first, segtree; vector< bool > visited; int n; LCA(vector<vector< int >> &adj, int root = 0) { n = adj.size(); height.resize(n); first.resize(n); euler.reserve(n * 2); visited.assign(n, false ); dfs(adj, root); int m = euler.size(); segtree.resize(m * 4); build(1, 0, m - 1); } void dfs(vector<vector< int >> &adj, int node, int h = 0) { visited[node] = true ; height[node] = h; first[node] = euler.size(); euler.push_back(node); for ( auto to : adj[node]) { if (!visited[to]) { dfs(adj, to, h + 1); euler.push_back(node); } } } void build( int node, int b, int e) { if (b == e) { segtree[node] = euler[b]; } else { int mid = (b + e) / 2; build(node << 1, b, mid); build(node << 1 | 1, mid + 1, e); int l = segtree[node << 1], r = segtree[node << 1 | 1]; segtree[node] = (height[l] < height[r]) ? l : r; } } int query( int node, int b, int e, int L, int R) { if (b > R || e < L) return -1; if (b >= L && e <= R) return segtree[node]; int mid = (b + e) >> 1; int left = query(node << 1, b, mid, L, R); int right = query(node << 1 | 1, mid + 1, e, L, R); if (left == -1) return right; if (right == -1) return left; return height[left] < height[right] ? left : right; } int lca( int u, int v) { int left = first[u], right = first[v]; if (left > right) swap(left, right); return query(1, 0, euler.size() - 1, left, right); } }; |
4. Practice Problems
- SPOJ: LCA
- SPOJ: DISQUERY
- TIMUS: 1471. Distance in the Tree
- CODEFORCES: Design Tutorial: Inverse the Problem
- CODECHEF: Lowest Common Ancestor
- SPOJ – Lowest Common Ancestor
- SPOJ – Ada and Orange Tree
- DevSkills – Motoku
- UVA 12655 – Trucks
- Codechef – Pishty and Tree
- UVA – 12533 – Joining Couples
- Codechef – So close yet So Far
- Codeforces – Drivers Dissatisfaction
- UVA 11354 – Bond
- SPOJ – Querry on a tree II
- Codeforces – Best Edge Weight
- Codeforces – Misha, Grisha and Underground
- SPOJ – Nlogonian Tickets
- Codeforces – Rowena Rawenclaws Diadem