--- 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"), ], } } ## Tutorial lol this code format is terrible ## Solving for All Roots (dfs twice) ```cpp template 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 S; F0R(i,n-1) { int a,b; re(a,b); S.ae(a,b); } S.init(n); } ``` ## Combining Subtrees (show why complexity is $O(N^2)$) ## Problems 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. ```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 } ```