DP on Trees
Imagine calculating a sports team's total score where each player's contribution depends on the players directly below them in the hierarchy — coaches depend on their assistants, assistants depend on their players. To compute the head coach's score, you first need every player's score, then every assistant's, and only then the coaches'. That bottom-up aggregation is exactly DP on Trees.
Trees have no cycles, which means the parent-child direction gives you a natural computation order. Process children before parents — that's DFS post-order. When you arrive at a node, all its children's DP values are already ready.
Basic Tree DP: Post-Order DFS
Root the tree at any node. In post-order DFS, a node is visited only after all its subtree descendants. So when you compute dp[node], every dp[child] is already available. The DP state dp[node] typically represents "the best answer for the subtree rooted at node".
Example — Maximum Path Sum: dp[node] = max sum of a downward path starting at this node. dp[node] = node.val + max(0, max(dp[child] for all children)). The overall answer might pass through a node as the highest point — sum up its two best child paths to find the diameter or max path.
Tree Knapsack — Selecting a Connected Subtree
Select at most k nodes from the tree to maximize total value, with the rule: if a node is selected, its parent must also be selected (the selected nodes form a connected subtree). State: dp[node][j] = max value using exactly j nodes from node's subtree, including node itself. Merge children one by one using a knapsack-style combination. With careful implementation, total time is \(O(n^2)\).
Rerooting — When Every Node Needs to Be the Root
Sometimes dp[node] should represent the answer for the whole tree when node is the root, not just its subtree. Computing this naively (re-root and re-run DFS for every node) is \(O(n^2)\).
The rerooting technique does it in two DFS passes:
Down-pass (DFS 1): Root at node 0, compute down[v] = subtree answer for v.
Up-pass (DFS 2): For each node v, compute up[v] = the best answer from outside v's subtree (from v's parent and the parent's other children). Combine down[v] and up[v] to get the full-tree answer rooted at v.
Example — Sum of Distances: For every node, find the total distance to all other nodes. Two DFS passes compute this for all n nodes in \(O(n)\) total.