We strongly recommend to refer below post as a prerequisite of this.
In the above post, we discussed the Heavy-light decomposition (HLD) with the help of below example.
Suppose we have an unbalanced tree (not necessarily a Binary Tree) of n nodes, and we have to perform operations on the tree to answer a number of queries, each can be of one of the two types:
- change(a, b): Update weight of the ath edge to b.
- maxEdge(a, b): Print the maximum edge weight on the path from node a to node b. For example maxEdge(5, 10) should print 25.
In this article implementation of same is discussed
Our line of attack for this problem is:
- Creating the tree
- Setting up the subtree size, depth and parent for each node (using a DFS)
- Decomposing the tree into disjoint chains
- Building up the segment tree
- Answering queries
1. Tree Creation: Implementation uses adjacency matrix representation of the tree, for the ease of understanding. One can use adjacency list rep with some changes to the source. If edge number e with weight w exists between nodes u and v, we shall store e at tree[u][v] and tree[v][u], and the weight w in a separate linear array of edge weights (n-1 edges).
2. Setting up the subtree size, depth and parent for each node: Next we do a DFS on the tree to set up arrays that store parent, subtree size and depth of each node. Another important thing we do at the time of DFS is storing the deeper node of every edge we traverse. This will help us at the time of updating the tree (change() query) .
3 & 4. Decomposing the tree into disjoint chains and Building Segment Tree Now comes the most important part: HLD. As we traverse the edges and reach nodes (starting from the root), we place the edge in the segment tree base, we decide if the node will be a head to a new chain (if it is a normal child) or will the current chain extend (special child), store the chain ID to which the node belongs, and store its place in the segment tree base (for future queries). The base for segment tree is built such that all edges belonging to the same chain are together, and chains are separated by light edges.
Illustration: We start at node 1. Since there wasn’t any edge by which we came to this node, we insert ‘-1’ as the imaginary edge’s weight, in the array that will act as base to the segment tree.
Next, we move to node 1’s special child, which is node 2, and since we traversed edge with weight 13, we add 13 to our base array. Node 2’s special child is node 6. We traverse edge with weight 25 to reach node 6. We insert in base array. And similarly we extend this chain further while we haven’t reached a leaf node (node 10 in our case).
Then we shift to a normal child of parent of the last leaf node, and mark the beginning of a new chain. Parent here is node 8 and normal child is node 11. We traverse edge with weight 6 and insert it into the base array. This is how we complete the base array for the segment tree.
Also remember that we need to store the position of every node in segment tree for future queries. Position of node 1 is 1, node 2 is 2, node 6 is 3, node 8 is 4, …, node 11 is 6, node 5 is 7, node 9 is 10, node 4 is 11 (1-based indexing).
5. Answering queries
We have discussed mexEdge() query in detail in previous post. For maxEdge(u, v), we find max weight edge on path from u to LCA and v to LCA and return maximum of two.
For change() query, we can update the segment tree by using the deeper end of the edge whose weight is to be updated. We will find the position of deeper end of the edge in the array acting as base to the segment tree and then start our update from that node and move upwards updating segment tree. Say we want to update edge 8 (between node 7 and node 9) to 28. Position of deeper node 9 in base array is 10, We do it as follows:
Below is C++ implementation of above steps.
Max edge between 11 and 9 is 30 After Change: max edge between 11 and 9 is 29 Max edge between 11 and 4 is 25 After Change: max edge between 11 and 4 is 23