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

Footnotes:

Author: Shi Shougang

Created: 2016-11-19 Sat 22:47

Emacs 24.3.1 (Org mode 8.2.10)

Validate