Heavy Light Decomposition
Table of Contents
Overview
The heavy-light (H-L) decomposition of a rooted tree is a method of partitioning of the vertices of the tree into disjoint paths (all vertices have degree two, except the endpoints of a path, with degree one) that gives important asymptotic time bounds for certain problems involving trees.1
Basic idea of the algorithm
- divide the tree into vertex-disjoint path (no two paths have a node in common)
- in another words, the path from any node to root can be broken into pieces, and there are no more than \(logN\) pieces
- use Segment tree/other data structures deal with the different paths, it can be up to \(O(logN)\) complexity
- any node A to any node B can be broken into two paths: A to LCA(A,B) and LCA(A,B) to B. Details about LCA(Lowest common ancestor): wiki, wcipeg wiki and Range Minimum Query and Lowest Common Ancestor
- the whole complexity: \(O(log^2 N)\)
Application
- The maximum number of the path between the two vertices
- The sum of the numbers in the path between two vertices
- Repainting edges path between two vertices
- and more
Examples
maximum edge weight on the path from node a to node b2
You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3…N-1. We will ask you to perfrom some instructions of the following form:
- 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.
The basic idea of the source codes:
- DFS the whole tree to get tree info(depth, subsize, etc.)
- construct a Segment tree using all nodes
- A easy LCA algorithm: find first node common in path from v to root and u to root
- max edge between the u and LCA(u,v): if they are in the same chain, just use segment tree get the max edge, if are not, continue to compute the max edge between chainhead(u) and LCA(u,v)
#include<bits/stdc++.h> using namespace std; #define N 1024 int tree[N][N]; // Matrix representing the tree /* a tree node structure. Every node has a parent, depth, subtree size, chain to which it belongs and a position in base array*/ struct treeNode { int par; // Parent of this node int depth; // Depth of this node int size; // Size of subtree rooted with this node int pos_segbase; // Position in segment tree base int chain; } node[N]; /* every Edge has a weight and two ends. We store deeper end */ struct Edge { int weight; // Weight of Edge int deeper_end; // Deeper end } edge[N]; /* we construct one segment tree, on base array */ struct segmentTree { int base_array[N], tree[6*N]; } s; // A function to add Edges to the Tree matrix // e is Edge ID, u and v are the two nodes, w is weight void addEdge(int e, int u, int v, int w) { /*tree as undirected graph*/ tree[u-1][v-1] = e-1; tree[v-1][u-1] = e-1; edge[e-1].weight = w; } // A recursive function for DFS on the tree // curr is the current node, prev is the parent of curr, // dep is its depth void dfs(int curr, int prev, int dep, int n) { /* set parent of current node to predecessor*/ node[curr].par = prev; node[curr].depth = dep; node[curr].size = 1; /* for node's every child */ for (int j=0; j<n; j++) { if (j!=curr && j!=node[curr].par && tree[curr][j]!=-1) { /* set deeper end of the Edge as this child*/ edge[tree[curr][j]].deeper_end = j; /* do a DFS on subtree */ dfs(j, curr, dep+1, n); /* update subtree size */ node[curr].size+=node[j].size; } } } // A recursive function that decomposes the Tree into chains void hld(int curr_node, int id, int *edge_counted, int *curr_chain, int n, int chain_heads[]) { /* if the current chain has no head, this node is the first node * and also chain head */ if (chain_heads[*curr_chain]==-1) chain_heads[*curr_chain] = curr_node; /* set chain ID to which the node belongs */ node[curr_node].chain = *curr_chain; /* set position of node in the array acting as the base to the segment tree */ node[curr_node].pos_segbase = *edge_counted; /* update array which is the base to the segment tree */ s.base_array[(*edge_counted)++] = edge[id].weight; /* Find the special child (child with maximum size) */ int spcl_chld = -1, spcl_edg_id; for (int j=0; j<n; j++) if (j!=curr_node && j!=node[curr_node].par && tree[curr_node][j]!=-1) if (spcl_chld==-1 || node[spcl_chld].size < node[j].size) spcl_chld = j, spcl_edg_id = tree[curr_node][j]; /* if special child found, extend chain */ if (spcl_chld!=-1) hld(spcl_chld, spcl_edg_id, edge_counted, curr_chain, n, chain_heads); /* for every other (normal) child, do HLD on child subtree as separate chain*/ for (int j=0; j<n; j++) { if (j!=curr_node && j!=node[curr_node].par && j!=spcl_chld && tree[curr_node][j]!=-1) { (*curr_chain)++; hld(j, tree[curr_node][j], edge_counted, curr_chain, n, chain_heads); } } } // A recursive function that constructs Segment Tree for array[ss..se). // si is index of current node in segment tree st int construct_ST(int ss, int se, int si) { // If there is one element in array, store it in current node of // segment tree and return if (ss == se-1) { s.tree[si] = s.base_array[ss]; return s.base_array[ss]; } // If there are more than one elements, then recur for left and // right subtrees and store the minimum of two values in this node int mid = (ss + se)/2; s.tree[si] = max(construct_ST(ss, mid, si*2), construct_ST(mid, se, si*2+1)); return s.tree[si]; } // A recursive function that updates the Segment Tree // x is the node to be updated to value val // si is the starting index of the segment tree // ss, se mark the corners of the range represented by si int update_ST(int ss, int se, int si, int x, int val) { if (ss > x || se <= x) { } else if (ss == x && ss == se-1) { s.tree[si] = val; } else { int mid = (ss + se)/2; s.tree[si] = max(update_ST(ss, mid, si*2, x, val), update_ST(mid, se, si*2+1, x, val)); } return s.tree[si]; } // A function to update Edge e's value to val in segment tree void change(int e, int val, int n) { update_ST(0, n, 1, node[edge[e].deeper_end].pos_segbase, val); } // A function to get the LCA of nodes u and v int LCA(int u, int v, int n) { /* array for storing path from u to root */ int LCA_aux[n+5]; // Set u is deeper node if it is not if (node[u].depth < node[v].depth) swap(u, v); /* LCA_aux will store path from node u to the root*/ memset(LCA_aux, -1, sizeof(LCA_aux)); while (u!=-1) { LCA_aux[u] = 1; u = node[u].par; } /* find first node common in path from v to root and u to root using LCA_aux */ while (v) { if (LCA_aux[v]==1) break; v = node[v].par; } return v; } /* A recursive function to get the minimum value in a given range of array indexes. The following are parameters for this function. index --> Index of current node in the segment tree. Initially ss & se --> Starting and ending indexes of the segment represented by current node, i.e., st[index] qs & qe --> Starting and ending indexes of query range */ int RMQUtil(int ss, int se, int qs, int qe, int index) { // If segment of this node is a part of given range, then return // the min of the segment if (qs <= ss && qe >= se-1) return s.tree[index]; // If segment of this node is outside the given range if (se-1 < qs || ss > qe) return -1; // If a part of this segment overlaps with the given range int mid = (ss + se)/2; return max(RMQUtil(ss, mid, qs, qe, 2*index), RMQUtil(mid, se, qs, qe, 2*index+1)); } // Return minimum of elements in range from index qs (quey start) to // qe (query end). It mainly uses RMQUtil() int RMQ(int qs, int qe, int n) { // Check for erroneous input values if (qs < 0 || qe > n-1 || qs > qe) { printf("Invalid Input"); return -1; } return RMQUtil(0, n, qs, qe, 1); } // A function to move from u to v keeping track of the maximum // we move to the surface changing u and chains // until u and v donot belong to the same int crawl_tree(int u, int v, int n, int chain_heads[]) { int chain_u, chain_v = node[v].chain, ans = 0; while (true) { chain_u = node[u].chain; /* if the two nodes belong to same chain, * we can query between their positions in the array * acting as base to the segment tree. After the RMQ, * we can break out as we have no where further to go */ if (chain_u==chain_v) { if (u==v); //trivial else ans = max(RMQ(node[v].pos_segbase+1, node[u].pos_segbase, n), ans); break; } /* else, we query between node u and head of the chain to which u belongs and later change u to parent of head of the chain to which u belongs indicating change of chain */ else { ans = max(ans, RMQ(node[chain_heads[chain_u]].pos_segbase, node[u].pos_segbase, n)); u = node[chain_heads[chain_u]].par; } } return ans; } void maxEdge(int u, int v, int n, int chain_heads[]) { int lca = LCA(u, v, n); int ans = max(crawl_tree(u, lca, n, chain_heads), crawl_tree(v, lca, n, chain_heads)); printf("%d\n", ans); } int main() { /* fill adjacency matrix with -1 to indicate no connections */ memset(tree, -1, sizeof(tree)); int n = 11; /* arguments in order: Edge ID, node u, node v, weight w*/ addEdge(1, 1, 2, 13); addEdge(2, 1, 3, 9); addEdge(3, 1, 4, 23); addEdge(4, 2, 5, 4); addEdge(5, 2, 6, 25); addEdge(6, 3, 7, 29); addEdge(7, 6, 8, 5); addEdge(8, 7, 9, 30); addEdge(9, 8, 10, 1); addEdge(10, 8, 11, 6); /* our tree is rooted at node 0 at depth 0 */ int root = 0, parent_of_root=-1, depth_of_root=0; /* a DFS on the tree to set up: * arrays for parent, depth, subtree size for every node; * deeper end of every Edge */ dfs(root, parent_of_root, depth_of_root, n); int chain_heads[N]; /*we have initialized no chain heads */ memset(chain_heads, -1, sizeof(chain_heads)); /* Stores number of edges for construction of segment tree. Initially we haven't traversed any Edges. */ int edge_counted = 0; /* we start with filling the 0th chain */ int curr_chain = 0; /* HLD of tree */ hld(root, n-1, &edge_counted, &curr_chain, n, chain_heads); /* ST of segregated Edges */ construct_ST(0, edge_counted, 1); /* Since indexes are 0 based, node 11 means index 11-1, 8 means 8-1, and so on*/ int u = 11, v = 9; cout << "Max edge between " << u << " and " << v << " is "; maxEdge(u-1, v-1, n, chain_heads); // Change value of edge number 8 (index 8-1) to 28 change(8-1, 28, n); cout << "After Change: max edge between " << u << " and " << v << " is "; maxEdge(u-1, v-1, n, chain_heads); v = 4; cout << "Max edge between " << u << " and " << v << " is "; maxEdge(u-1, v-1, n, chain_heads); // Change value of edge number 5 (index 5-1) to 22 change(5-1, 22, n); cout << "After Change: max edge between " << u << " and " << v << " is "; maxEdge(u-1, v-1, n, chain_heads); return 0; }
the maximum edge cost on the path from node a to node b3
You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3…N-1. We will ask you to perfrom some instructions of the following form:
- CHANGE i ti : change the cost of the i-th edge to ti
- QUERY a b : ask for the maximum edge cost on the path from node a to node b
- solution 4
- DFS the whole tree to get tree info(depth, subsize, etc.)
- construct a Segment tree using all nodes
- A LCA algorithm: use dynamic programming idea store the prev node
#include <cstdio> #include <vector> using namespace std; #define root 0 #define N 10100 #define LN 14 vector <int> adj[N], costs[N], indexx[N]; int baseArray[N], ptr; int chainNo, chainInd[N], chainHead[N], posInBase[N]; int depth[N], pa[LN][N], otherEnd[N], subsize[N]; int st[N*6], qt[N*6]; /* * make_tree: * Used to construct the segment tree. It uses the baseArray for construction */ void make_tree(int cur, int s, int e) { if(s == e-1) { st[cur] = baseArray[s]; return; } int c1 = (cur<<1), c2 = c1 | 1, m = (s+e)>>1; make_tree(c1, s, m); make_tree(c2, m, e); st[cur] = st[c1] > st[c2] ? st[c1] : st[c2]; } /* * update_tree: * Point update. Update a single element of the segment tree. */ void update_tree(int cur, int s, int e, int x, int val) { if(s > x || e <= x) return; if(s == x && s == e-1) { st[cur] = val; return; } int c1 = (cur<<1), c2 = c1 | 1, m = (s+e)>>1; update_tree(c1, s, m, x, val); update_tree(c2, m, e, x, val); st[cur] = st[c1] > st[c2] ? st[c1] : st[c2]; } /* * query_tree: * Given S and E, it will return the maximum value in the range [S,E) */ void query_tree(int cur, int s, int e, int S, int E) { if(s >= E || e <= S) { qt[cur] = -1; return; } if(s >= S && e <= E) { qt[cur] = st[cur]; return; } int c1 = (cur<<1), c2 = c1 | 1, m = (s+e)>>1; query_tree(c1, s, m, S, E); query_tree(c2, m, e, S, E); qt[cur] = qt[c1] > qt[c2] ? qt[c1] : qt[c2]; } /* * query_up: * It takes two nodes u and v, condition is that v is an ancestor of u * We query the chain in which u is present till chain head, then move to next chain up * We do that way till u and v are in the same chain, we query for that part of chain and break */ int query_up(int u, int v) { if(u == v) return 0; // Trivial int uchain, vchain = chainInd[v], ans = -1; // uchain and vchain are chain numbers of u and v while(1) { uchain = chainInd[u]; if(uchain == vchain) { // Both u and v are in the same chain, so we need to query from u to v, update answer and break. // We break because we came from u up till v, we are done if(u==v) break; query_tree(1, 0, ptr, posInBase[v]+1, posInBase[u]+1); // Above is call to segment tree query function if(qt[1] > ans) ans = qt[1]; // Update answer break; } query_tree(1, 0, ptr, posInBase[chainHead[uchain]], posInBase[u]+1); // Above is call to segment tree query function. We do from chainHead of u till u. That is the whole chain from // start till head. We then update the answer if(qt[1] > ans) ans = qt[1]; u = chainHead[uchain]; // move u to u's chainHead u = pa[0][u]; //Then move to its parent, that means we changed chains } return ans; } /* * LCA: * Takes two nodes u, v and returns Lowest Common Ancestor of u, v */ int LCA(int u, int v) { if(depth[u] < depth[v]) swap(u,v); int diff = depth[u] - depth[v]; for(int i=0; i<LN; i++) if( (diff>>i)&1 ) u = pa[i][u]; if(u == v) return u; for(int i=LN-1; i>=0; i--) if(pa[i][u] != pa[i][v]) { u = pa[i][u]; v = pa[i][v]; } return pa[0][u]; } void query(int u, int v) { /* * We have a query from u to v, we break it into two queries, u to LCA(u,v) and LCA(u,v) to v */ int lca = LCA(u, v); int ans = query_up(u, lca); // One part of path int temp = query_up(v, lca); // another part of path if(temp > ans) ans = temp; // take the maximum of both paths printf("%d\n", ans); } /* * change: * We just need to find its position in segment tree and update it */ void change(int i, int val) { int u = otherEnd[i]; update_tree(1, 0, ptr, posInBase[u], val); } /* * Actual HL-Decomposition part * Initially all entries of chainHead[] are set to -1. * So when ever a new chain is started, chain head is correctly assigned. * As we add a new node to chain, we will note its position in the baseArray. * In the first for loop we find the child node which has maximum sub-tree size. * The following if condition is failed for leaf nodes. * When the if condition passes, we expand the chain to special child. * In the second for loop we recursively call the function on all normal nodes. * chainNo++ ensures that we are creating a new chain for each normal child. */ void HLD(int curNode, int cost, int prev) { if(chainHead[chainNo] == -1) { chainHead[chainNo] = curNode; // Assign chain head } chainInd[curNode] = chainNo; posInBase[curNode] = ptr; // Position of this node in baseArray which we will use in Segtree baseArray[ptr++] = cost; int sc = -1, ncost; // Loop to find special child for(int i=0; i<adj[curNode].size(); i++) if(adj[curNode][i] != prev) { if(sc == -1 || subsize[sc] < subsize[adj[curNode][i]]) { sc = adj[curNode][i]; ncost = costs[curNode][i]; } } if(sc != -1) { // Expand the chain HLD(sc, ncost, curNode); } for(int i=0; i<adj[curNode].size(); i++) if(adj[curNode][i] != prev) { if(sc != adj[curNode][i]) { // New chains at each normal node chainNo++; HLD(adj[curNode][i], costs[curNode][i], curNode); } } } /* * dfs used to set parent of a node, depth of a node, subtree size of a node */ void dfs(int cur, int prev, int _depth=0) { pa[0][cur] = prev; depth[cur] = _depth; subsize[cur] = 1; for(int i=0; i<adj[cur].size(); i++) if(adj[cur][i] != prev) { otherEnd[indexx[cur][i]] = adj[cur][i]; dfs(adj[cur][i], cur, _depth+1); subsize[cur] += subsize[adj[cur][i]]; } } int main() { int t; scanf("%d ", &t); while(t--) { ptr = 0; int n; scanf("%d", &n); // Cleaning step, new test case for(int i=0; i<n; i++) { adj[i].clear(); costs[i].clear(); indexx[i].clear(); chainHead[i] = -1; for(int j=0; j<LN; j++) pa[j][i] = -1; } for(int i=1; i<n; i++) { int u, v, c; scanf("%d %d %d", &u, &v, &c); u--; v--; adj[u].push_back(v); costs[u].push_back(c); indexx[u].push_back(i-1); adj[v].push_back(u); costs[v].push_back(c); indexx[v].push_back(i-1); } chainNo = 0; dfs(root, -1); // We set up subsize, depth and parent for each node HLD(root, -1, -1); // We decomposed the tree and created baseArray make_tree(1, 0, ptr); // We use baseArray and construct the needed segment tree // Below Dynamic programming code is for LCA. for(int i=1; i<LN; i++) for(int j=0; j<n; j++) if(pa[i-1][j] != -1) pa[i][j] = pa[i-1][pa[i-1][j]]; while(1) { char s[100]; scanf("%s", s); if(s[0]=='D') { break; } int a, b; scanf("%d %d", &a, &b); if(s[0]=='Q') { query(a-1, b-1); } else { change(a-1, b); } } } }
More
- Introduction and implementation by geeksforgeeks
- Description and application: http://e-maxx.ru/algo/heavy_light
- Heavy Light Decomposition Description and application: https://blog.anudeep2011.com/heavy-light-decomposition/