State = a subtree (plus a few flags about its root). Compute children before parents: recursion, or a BFS order processed backwards (no stack overflow). Rerooting gives the answer for every root in with a second, top-down pass. Size-bounded merging makes “choose vertices” DPs instead of .

Patterns

ProblemStateMerge child into
max weight independent set ( taken?),
min vertex cover,
min dominating setthree-state merge
max matchinggreedy: match a leaf with its parent (or = matched to a child)
diameter / longest pathtwo deepest child depthsanswer
number of connected subgraphs containing
colourings with constraints
exactly chosen vertices / edgessize-bounded knapsack merge
depth-indexed counts ( vertices at depth )long-path decomposition, (Long-Path)
convex piecewise-linear cost per vertexslope-trick heapsmerge heaps small-to-large (Slope Trick)
order of visiting children matters (min total waiting time)exchange argument on childrensort by ratio, then DP (Exchange)
distinct colours/values per subtreeDSU on tree, or Euler tour + offline queries—
delete edges to satisfy a global constraintfold the constraint (parity, remaining budget, …) into the state
vector<int> order{root}, par(n, -1);                       // iterative skeleton
for (int i = 0; i < sz(order); i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], order.push_back(v);
for (int i = n - 1; i >= 0; i--) { int u = order[i]; /* children of u are finished; combine them into f[u] */ }

Tree knapsack in

= best value of a connected set of vertices in ‘s subtree containing (or “choose items in the subtree”, same merge). Merge children with loops bounded by the current sizes, capped at :

vector<ll> treeKnap(const vector<vector<int>>& adj, const vector<ll>& val, int root, int K) {   // K >= 1
    int n = sz(adj); vector<int> order{root}, par(n, -1);
    for (int i = 0; i < sz(order); i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], order.push_back(v);
    vector<vector<ll>> dp(n);
    for (int i = n - 1; i >= 0; i--) {
        int u = order[i]; vector<ll> cur{0, val[u]};       // cur[j], j >= 1 includes u
        for (int v : adj[u]) if (v != par[u]) {
            auto& d = dp[v];                                // d[0] = 0: take nothing from v's subtree
            vector<ll> nx(min<int>(K, sz(cur) + sz(d) - 2) + 1, LLONG_MIN); nx[0] = 0;
            for (int a = 1; a < sz(cur); a++) for (int b = 0; b < sz(d) && a + b < sz(nx); b++) nx[a + b] = max(nx[a + b], cur[a] + d[b]);
            cur.swap(nx); vector<ll>().swap(d);
        }
        if (sz(cur) > K + 1) cur.resize(K + 1);
        dp[u] = move(cur);
    }
    return dp[root];                                        // dp[root][j]: best connected j-set containing root
}

Dependency knapsack (choose a set closed under “child chosen parent chosen” with total weight ) in : in preorder, skipping vertex skips its whole subtree (a contiguous block of size ).

// pre = preorder, sub = subtree sizes; g has n + 1 rows of W + 1 zeros
for (int i = n - 1; i >= 0; i--) {
    int v = pre[i];
    for (int w = 0; w <= W; w++) {
        g[i][w] = g[i + sub[v]][w];                                        // skip v and its subtree
        if (w >= wt[v]) g[i][w] = max(g[i][w], g[i + 1][w - wt[v]] + val[v]);   // take v, continue inside
    }
}
// answer g[0][W]; forests: add a virtual root of weight 0 and use capacity W, index from the virtual root's child
// weights must be nonnegative; to force the (virtual) root taken, read g[1][W - wt[root]] + val[root] instead of g[0][W]

Rerooting

Each vertex sends its neighbour a contribution summarising the component on its side. Let merge combine contributions (associative, with identity) and addRoot(x, v) turn the merged contributions of ‘s other neighbours into ‘s contribution. Pass 1 computes contributions towards the root; pass 2 computes, for each child, the merge of all other neighbours via prefix/suffix aggregates, so no inverse is needed.

template<class T, class M, class R>
vector<T> reroot(const vector<vector<int>>& adj, T id, M merge, R addRoot) {
    int n = sz(adj); vector<int> order{0}, par(n, -1);
    for (int i = 0; i < sz(order); i++) for (int v : adj[order[i]]) if (v != par[order[i]]) par[v] = order[i], order.push_back(v);
    vector<T> down(n, id), up(n, id), ans(n, id);          // down[u]: merge of children's contributions
    for (int i = n - 1; i >= 0; i--) { int u = order[i]; for (int v : adj[u]) if (v != par[u]) down[u] = merge(down[u], addRoot(down[v], v)); }
    for (int u : order) {                                    // up[v]: merge of u's neighbours except child v
        int k = sz(adj[u]); vector<T> c(k), suf(k + 1, id);
        for (int i = 0; i < k; i++) { int v = adj[u][i]; c[i] = v == par[u] ? addRoot(up[u], v) : addRoot(down[v], v); }
        for (int i = k - 1; i >= 0; i--) suf[i] = merge(c[i], suf[i + 1]);
        ans[u] = suf[0]; T pre = id;
        for (int i = 0; i < k; i++) { if (adj[u][i] != par[u]) up[adj[u][i]] = merge(pre, suf[i + 1]); pre = merge(pre, c[i]); }
    }
    return ans;                                              // merge of all neighbours' contributions, u as root
}
// sum of distances: T = pair(sum, cnt), merge = add, addRoot = [](T x, int) { return T{x.first + x.second + 1, x.second + 1}; }
// eccentricity:     T = int (id 0),     merge = max, addRoot = [](int x, int) { return x + 1; }

Shortcut: eccentricities on a tree are for diameter endpoints (Tree Basics).

When merge is specifically max (not a general associative op), the prefix/suffix machinery above can be skipped: keep just the two largest child values at each vertex, and “all neighbours except child ” is the largest of the two unless it came from , in which case it’s the second-largest.

Other techniques on trees

  • Small to large (merge the smaller map into the larger) for per-subtree sets: moves (Small to Large).
  • Segment tree merging for per-subtree value-indexed arrays (Segment Tree Merging).
  • Path DP (“count paths with property P”): centroid decomposition, or DP with combined at each vertex.
  • DP over the Euler tour (dependency knapsack above, or “choose disjoint subtrees”).
  • Dynamic DP when edge weights or vertex values change: write the transition as a matrix along heavy paths (Dynamic DP).