DP on Trees

Org charts, file systems, comment threads and network topologies are trees. Many “best choice” questions on them, like inviting the most people with no boss–employee pair, become linear once each node combines answers from its children.

advanced⏱ 12 min read
01

Learn

The idea, the mechanics and the cost.

01Answers bubble up from the leaves

On a rooted tree, the natural subproblem is “the answer for the subtree of v”. Subtrees of different children do not overlap, and the subtree of v is just v plus its children’s subtrees. So:

  • the state lives on vertices: dp[v], sometimes with a small extra flag;
  • the order is post-order DFS: finish every child before its parent;
  • each vertex combines its children’s values, so every edge is looked at once: O(n) in total.

The simplest example is subtree size: size[v] = 1 + Σ size[child]. Leaves have size 1, and the root gets n. It looks trivial, but sizes are used everywhere: counting pairs through an edge, finding a centroid, heavy-light decomposition.

Recursion is the easiest way to write it; for very deep trees (a path of 10⁵ vertices) use an explicit stack to avoid stack overflow.

The simplest tree DP: subtree size122132495161748491size[v] = 1 + Σ size[child]

02Two states per vertex: take it or skip it

Maximum independent set: choose as many vertices as possible so that no two chosen vertices are joined by an edge. On a general graph this is NP-hard; on a tree it is easy.

One number per vertex is not enough, because the parent needs to know whether the child itself was chosen. So keep two:

  • take[v] = best size in v’s subtree with v chosen. Then no child may be chosen: take[v] = 1 + Σ skip[c].
  • skip[v] = best size without v. Then each child is free: skip[v] = Σ max(take[c], skip[c]).

Leaves have take = 1, skip = 0. The answer is max(take[root], skip[root]).

This “extra flag in the state” pattern is the heart of tree DP. Whenever a choice at v restricts its children (colours, matching, guards on vertices), add the choice to the state.

A parent combines its children’s answersvc11, 0c21, 1c32, 2take[v] = 1 + Σ skip[c]v taken ⇒ no childskip[v] = Σ max(take, skip)v skipped ⇒ each child is freechildren before parent: post-order DFS, O(n)

03Running it and getting the set back

On the example tree rooted at 4, the post-order fills leaves first (1, 0), then 1 gets (1, 1), 7 gets (2, 2), 3 gets (1, 1), 8 gets (2, 2), and the root gets take = 1 + 2 + 2 = 5, skip = 2 + 2 = 4. The answer is 5.

To list the vertices, walk down from the root: if v is allowed and take[v] ≥ skip[v], choose v and forbid its children; otherwise skip v and let the children decide for themselves. Here that picks {4, 6, 1, 3, 5}.

The same idea of two passes (up for values, down for choices) solves tree matching and minimum vertex cover. A second family, rerooting, adds a downward pass that passes “the answer from outside my subtree” to each child, so you can get, for example, the sum of distances from every vertex in O(n).

Max independent set: (take, skip) at every vertex11,121,031,145,451,061,072,282,291,0root: max(5, 4) = 5chosen: 4, 6, 1, 3, 5
Each label is (take, skip). Green vertices form the maximum independent set.

Cost at a glance

Any tree DP that combines childrenO(n)
Recover the chosen setO(n)
Rerooting (answer for every root)O(n)

Remember

  1. Subtrees of different children are independent, so dp[v] is computed from the children in post-order, O(n) total.
  2. When a choice at v restricts its children, keep one value per choice, like take[v] and skip[v].
  3. Values go up; to recover the actual choices, walk back down from the root.
02

Play

Step through the algorithm, then try it on your own input.

👀 What to watch: Watch t:take s:skip appear under each vertex in post-order, leaves first, then the downward pass colours the chosen set.

Interactive visualizerfocus here, then space ← →

DP on trees: maximum independent set

123456789
Maximum Independent Set: pick the most vertices such that no two are adjacent. On a tree, DFS + combining children solves it in O(n).

Pseudocode

 1 post-order (children before parent): 2   take[v]  = 1 + Σ skip[child]        // v chosen ⇒ children excluded 3   skip[v]  = Σ max(take[c], skip[c])  // v free ⇒ children may be chosen 4 answer at root = max(take[root], skip[root]) 5 walk down choosing take/skip consistently to recover the set
1 / 1
03

Check

Three questions. Pick an answer to see why.

Q1

A vertex has three leaf children. What are its take and skip?

Q2

In what order must tree DP values be computed?

Q3

Why does the same take/skip idea not work directly on a graph with cycles?

04

Practice

Real problems to lock it in, easiest first.