184 lines
4.4 KiB
Text
184 lines
4.4 KiB
Text
---
|
|
id: dp-trees
|
|
title: "Dynamic Programming on Trees"
|
|
author: Michael Cao
|
|
prerequisites:
|
|
- dfs
|
|
- intro-dp
|
|
description: "Using subtrees as subproblems."
|
|
frequency: 2
|
|
---
|
|
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", "Directory Traversal", "814", "Normal", 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 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>
|
|
|
|
## Sample Solution
|
|
|
|
## Solving for All Roots
|
|
|
|
<Problems problems={metadata.problems.allRoots} />
|
|
|
|
(dfs twice)
|
|
|
|
<Spoiler title="Solution">
|
|
|
|
<LanguageSection>
|
|
|
|
<CPPSection>
|
|
|
|
```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);
|
|
}
|
|
```
|
|
|
|
</CPPSection>
|
|
|
|
</LanguageSection>
|
|
|
|
|
|
</Spoiler>
|
|
|
|
## Combining Subtrees
|
|
|
|
(show why complexity is $O(N^2)$)
|
|
|
|
## Problems
|
|
|
|
<Info 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>
|
|
|
|
<Problems 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>
|