Dynamic Programming on Trees
When the subproblems of a dynamic program are rooted subtrees, a single post-order DFS solves the whole thing in : each node combines the already-computed answers of its children. We meet the archetype — maximum-weight independent set on a tree — then the "path through a node" pattern behind tree diameter and maximum path sum, and finally rerooting, which computes a per-node answer for every node as root in with two passes.
╌╌╌╌
Dynamic programming works whenever a problem decomposes into overlapping subproblems ordered so that each can be solved from smaller ones already in hand. On a sequence the natural subproblems are prefixes; on an interval they are subintervals. On a tree the natural subproblems are rooted subtrees, and the ordering that makes them solvable is the post-order traversal, which visits every node only after all of its children. Root the tree anywhere, define an answer for the subtree hanging below each node , and a single depth-first sweep fills the entire table.
The defining feature of these problems is that the recurrence is local: the answer at depends only on the answers at 's children,
never on grandchildren directly and never on the rest of the tree. Because the DFS touches each node and each edge exactly once and does work per child, the whole computation runs in time for a tree on nodes.1 The work is in choosing the state: what must remember about the subtree so that a parent can combine children without re-descending into them. This is the usual optimal-substructure question, specialized to subtrees.
The archetype: maximum-weight independent set
Let each node of a tree carry a weight . An independent set is a set of nodes no two of which are adjacent; we want one of maximum total weight. On a general graph this is NP-hard, but on a tree dynamic programming solves it in linear time, the canonical illustration of the whole technique.2
The idea is to make the state record whether itself is used, because that is exactly the one fact a parent needs in order to decide about itself. Define, for the subtree rooted at , two values:
- , the best independent set of 's subtree in which is not taken;
- , the best one in which is taken.
If is not taken, each child is free to be taken or not, so we keep the better of its two options. If is taken, no child may be taken, so each child must contribute its :
The base case falls out for free: a leaf has no children, so and . The answer for the whole tree is . This is House Robber III, where weights are the money in each house and the adjacency constraint forbids robbing a parent and its child on the same night.
- 1if then
- 2return
- 3taken
- 4excluded
- 5for each child of do
- 6
- 7child free
- 8child forbidden
- 9return
Correctness is an induction on subtree size: assuming the recursive calls return correct pairs for every child, the two formulas above exhaust the only two choices for and combine each child's correct sub-answer optimally, so the returned pair is correct for . The procedure visits each node once and spends per child, hence total. The state, taken vs. not taken, is the part worth remembering: a single extra bit per node turns an intractable graph problem into a linear-time tree sweep.3
Paths through a node: diameter and maximum path sum
A second pattern arises when the quantity we care about is a path, not a set. The diameter of a tree is the number of edges on its longest path; the maximum path sum (where nodes carry values, possibly negative) is the largest total along any path. Neither is a clean subtree quantity, because the optimal path may bend at some node, descending into two different children.
The resolution is the signature move of tree DP on paths. At each node , let be the best downward path that starts at and goes into a single subtree. A child extends to . The best path that bends at combines its two best children:
for the two children with the largest downward values. Here is the trap that catches everyone the first time:
- 1if then
- 2return
- 3drop negative branches
- 4
- 5bend here: both sides
- 6returnextendable: one side
For Binary Tree Maximum Path Sum the prunes branches that would only hurt the total; the global records the best bend seen anywhere. For Diameter of Binary Tree the same skeleton applies with edge counts in place of values: and the diameter is the largest over all nodes . Both run in : one post-order pass, per node.
Rerooting: an answer for every root in
The hardest variant asks for a quantity computed with each node in turn as the
root: for every node , say, the sum of distances from to all
other nodes. Re-running an DFS from each of the roots costs .
Rerooting (also called the all-roots
or re-root
technique) computes all
answers in total, with two DFS passes: one down, one up.4
Take Sum of Distances in Tree. Fix an arbitrary root and let be the number of nodes in 's subtree and the sum of distances from to every node inside its own subtree. A post-order pass computes both, since a child at distance contributes (every node under is one edge farther from than from ):
That gives the true global answer only at the root, where the subtree is the whole tree: . The second pass pushes the answer from a parent to each child in . Moving the root from to an adjacent child , the nodes on 's side each get one closer (distance drops by ) and the remaining nodes each get one farther:
Subtract the subtree's contribution, add the rest: the entire adjustment is a single formula, so the down-pass plus the up-pass together are .
A related linear-time tree DP is Distribute Coins in Binary Tree: each node returns to its parent the net coins it must send up or pull down, the signed excess summed over its subtree, and the total number of moves is the sum of absolute flows along every edge, accumulated in one post-order pass. Same shape: a local return value, a global accumulator, time.
Takeaways
- On a tree, the natural DP subproblems are rooted subtrees, solved by a single post-order DFS that combines each node's children in , hence overall, since every node and edge is processed once.
- The state must capture exactly what a parent needs. For maximum-weight independent set (House Robber III) that is one bit, taken vs. not taken: and .
- The path-through-a-node pattern (diameter, max path sum) returns one thing and updates another: return the single best downward extension to the parent, but update a global max with the two-child bent path, never returning the bent path.
- Rerooting computes a per-root answer for all nodes in via two
passes: a down-pass fixes the root's answer from subtree aggregates, an up-pass
transfers it edge-by-edge with a
subtract my subtree, add the rest
adjustment. - The recurring design questions are always the same: what does a node return to its parent, and what aggregate must the subtree cache so the combine stays .
Footnotes
- Erickson, Ch. — Dynamic Programming (trees): subtree subproblems solved bottom-up by post-order traversal in . ↩
- Skiena, § — Dynamic Programming on Trees: maximum independent set on trees as the linear-time archetype of tree DP. ↩
- CLRS, Ch. 15 — Dynamic Programming: optimal substructure and the combination of subproblem solutions, instantiated here on rooted subtrees. ↩
- Skiena, § — Dynamic Programming on Trees: the all-roots / rerooting technique computing every node's answer in with two DFS passes. ↩
╌╌ END ╌╌