---
id: dp-trees
title: "Dynamic Programming on Subtrees"
author: Michael Cao
prerequisites: 
 - Silver - Depth First Search
 - Gold - Introduction to Dynamic Programming
description: What it sounds like.
frequency: 1
---
import { Problem } from "../models";

export const metadata = {
  problems: {
    sample: [
      new Problem("CSES", "Tree Matching", "1130", "Easy", false, ["DP"], ""),
    ],
    allRoots: [
      new Problem("AC", "Subtree", "https://atcoder.jp/contests/dp/tasks/dp_v", "Normal", false, [], ""),
    ],
    usaco: [
      new Problem("AC", "Independent Set", "https://atcoder.jp/contests/dp/tasks/dp_p", "Easy", false, [], ""),
      new Problem("Gold", "Barn Painting", "766", "Easy", false, ["DP"], "similar to independent set on tree"),
      new Problem("ojuz", "COCI - Dzumbus", "COCI19_dzumbus", "Hard", false, [], ""),
      new Problem("Gold", "Delegation", "1019", "Hard", false, ["Greedy"], ""),
      new Problem("Plat", "Delegation", "1020", "Hard", false, ["DP", "Binary Search"], ""),
      new Problem("CF", "Ostap & Tree", "problemset/problem/735/E", "Hard", false, ["DP"], "$O(NK)$ I think"),
      new Problem("CSES", "Creating Offices", "1752", "Hard", false, ["Greedy"], "equivalent to BOI - Cat in a Tree"),
    ],
  }
}

<problems-list problems={metadata.problems.sample} />

## Tutorial

<resources>
  <resource source="CF" title="DP on Trees" url="blog/entry/20935"> </resource>
  <resource source="Philippines" title="DP on Trees and DAGs" url="https://noi.ph/training/weekly/week5.pdf">lol this code format is terrible </resource>
</resources>

## Solving for All Roots

<problems-list problems={metadata.problems.allRoots} />

(dfs twice)

<spoiler title="Solution">

```cpp
template<int SZ> struct SubtreeDP {
  int par[SZ]; vi adj[SZ];
  void ae(int a, int b) { adj[a].pb(b), adj[b].pb(a); }
  struct T { 
    mi v = 1;
    T& operator+=(const T& b) { v *= b.v; return *this; }
    void tran() { ++v; }
  };
  T up[SZ], down[SZ];
  void dfs(int x) {
    trav(t,adj[x]) if (t != par[x]) {
      par[t] = x; dfs(t);
      down[x] += down[t];
    }
    down[x].tran();
  }
  void dfs2(int x) {
    {
      T pre = up[x];
      F0R(i,sz(adj[x])) {
        int c = adj[x][i]; if (c == par[x]) continue;
        up[c] += pre; pre += down[c];
      }
    }
    {
      T pre;
      R0F(i,sz(adj[x])) {
        int c = adj[x][i]; if (c == par[x]) continue;
        up[c] += pre; pre += down[c];
      }
    }
    F0R(i,sz(adj[x])) {
      int c = adj[x][i]; if (c == par[x]) continue;
      up[c].tran(); dfs2(c);
    }
  }
  T getSub(int x, int y) { return par[x] == y ? down[x] : up[y]; }
  void init(int n) {
    par[1] = 0; dfs(1); dfs2(1);
    FOR(i,1,n+1) {
      T p = T(); trav(t,adj[i]) p += getSub(t,i);
      ps(p.v);
    }
  }
};

int main() {
  setIO(); int n; re(n,MOD);
  SubtreeDP<MX> S;
  F0R(i,n-1) {
    int a,b; re(a,b);
    S.ae(a,b);
  }
  S.init(n);
}
```

</spoiler>

## Combining Subtrees

(show why complexity is $O(N^2)$)

## Problems

<info-block title="Pro Tip">

Don't just dive into trying to figure out a DP state and transitions -- make some observations if you don't see any obvious DP solution! Also, sometimes a greedy strategy suffices.

</info-block>

<problems-list problems={metadata.problems.usaco} />

<spoiler title="Ostap & Tree">

```cpp
vmi yes[101], no[101];
int n,k;
vi adj[MX];
 
void dfs(int x, int y) {
  yes[x] = no[x] = {1}; // black, not black
  // dist of closest good vertex
  // or dist of farthest bad vertex
  auto ad = [](vmi& a, int b, mi c) {
    while (sz(a) <= b) a.pb(0);
    a[b] += c;
  };
  trav(t,adj[x]) if (t != y) {
    dfs(t,x);
    yes[t].insert(begin(yes[t]),0);
    no[t].insert(begin(no[t]),0);
    if (sz(no[t]) > k+1) no[t].pop_back();
    vmi YES, NO;
    F0R(i,sz(yes[x])) F0R(j,sz(yes[t])) ad(YES,min(i,j),yes[x][i]*yes[t][j]);
    F0R(i,sz(no[x])) F0R(j,sz(no[t])) ad(NO,max(i,j),no[x][i]*no[t][j]);
    auto yesNo = [&](vmi good, vmi bad) {
      F0R(i,sz(good)) F0R(j,sz(bad)) {
        if (i+j <= k) ad(YES,i,good[i]*bad[j]);
        else ad(NO,j,good[i]*bad[j]);
      }
    };
    yesNo(yes[x],no[t]); yesNo(yes[t],no[x]);
    swap(yes[x],YES); swap(no[x],NO);
  }
}
 
int main() {
  setIO(); re(n,k);
  F0R(i,n-1) {
    int a,b; re(a,b);
    adj[a].pb(b), adj[b].pb(a);
  }
  dfs(1,0);
  mi ans = 0;
  trav(t,yes[1]) ans += t;
  ps(ans);
  // you should actually read the stuff at the bottom
}
```

</spoiler>
