##树形$DP$专题 ### 【$T1$】求一颗树的直径 **难度 ★** **[$AcWing$ $1072$. 树的最长路径](https://www.cnblogs.com/littlehb/p/15784687.html)** **难度 ★★** **[$AcWing$ $1073$. 树的中心](https://www.cnblogs.com/littlehb/p/15786805.html)** **[$AcWing$ $1075$. 数字转换](https://www.cnblogs.com/littlehb/p/15787542.html)** #### 法一:$dfs$来回两遍 从任意一点出发找最长$A$,再从$A$找最长;和树形$DP$没有一丁点关系 **缺点**:不能处理负权边 ```cpp {.line-numbers} void dfs1(int u, int sum) { st[u] = 1; if (sum > ans) { ans = sum; // 记录最大距离 t1 = u; // 记录最远的点t1 } for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (st[j]) continue; dfs1(j, sum + w[i]); } } void dfs2(int u, int sum) { st[u] = 1; if (sum > ans) { ans = sum; // 记录最大距离 t2 = u; // 记录最远的点t2 } for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (st[j]) continue; dfs2(j, sum + w[i]); } } ... dfs1(1, 0); // 先找到点距离点1最远的点t ... dfs2(t1, 0); // 找到距离点t最远的点 ``` #### 法二:树形$DP$ $d1[u]$表示到$u$的最长路径。$d2[u]$表示到$u$的第二长路径,那么易得 $d1[u]+ d2[u]$即为经过$u$的最长路径 ```cpp {.line-numbers} void dfs(int u) { st[u] = 1; for (int i = h[u]; ~i; i = ne[i]) { int j = e[i]; if (st[j]) continue; // 走j子树 dfs(j); // d1[u]:最长路径,d2[u]:次长路径 if (d1[j] + w[i] >= d1[u]) d2[u] = d1[u], d1[u] = d1[j] + w[i]; // 最长路转移 else if (d1[j] + w[i] > d2[u]) d2[u] = d1[j] + w[i]; // 次长路转移 } // 更新结果 ans = max(ans, d1[u] + d2[u]); } ``` ###【$T2$】覆盖一棵树的最大、最小权值 **难度 ★** **[$AcWing$ $323$ 战略游戏](https://www.cnblogs.com/littlehb/p/15788291.html)** **难度★** **[$AcWing$ $285$. 没有上司的舞会](https://www.cnblogs.com/littlehb/p/15468981.html)** **对比:** 没有上司的舞会:每条边上 **最多** 选择一个点,**求最大权值** 战略游戏:每条边上 **最少** 选择一条点,**求最小权值** **难度★★★** **[$AcWing$ $1077$. 皇宫看守](https://www.cnblogs.com/littlehb/p/15788813.html)** ###【$T3$】保留一定数量的点、边,求方案数 **[$AcWing$ $1074$ . 二叉苹果树 ](https://www.cnblogs.com/littlehb/p/15788100.html)** **[计蒜客 $Mila$的苹果树](https://www.jisuanke.com/problem/T3702)** 【**本质是就是二叉苹果树的变形**】 ```cpp {.line-numbers} #include using namespace std; typedef long long LL; const int N = 2100, M = N << 1; const int MOD = 998244353; int e[M], h[N], idx, ne[M]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int n; int limit[N]; // 以u为根的子树中数量上限 int st[N]; // 是不是访问过 LL f[N][N]; void dfs(int u) { st[u] = 1; // 访问过了 f[u][0] = 1; // u子树中,放0个苹果的方案是1,此时只能是不管有多少个子结点,都是啥也不能放,方案唯一 if (limit[u]) f[u][1] = 1; // 在u子树中,如果只放1个的话,那么必然是放在u节点上,否则u子树就不存在的,此时方案数是1 for (int i = h[u]; ~i; i = ne[i]) { int v = e[i]; if (st[v]) continue; dfs(v); // 分组背包 for (int j = limit[u]; j >= 0; j--) // 枚举u子树体积,倒序降维 // Q:为什么这里k=0开始是错的呢? for (int k = 1; k <= min(j, limit[v]); k++) // 枚举决策,v子树中放资源k个 f[u][j] = (f[u][j] + f[v][k] * f[u][j - k]) % MOD; } } int main() { freopen("appletree.in", "r", stdin); freopen("appletree.out", "w", stdout); memset(h, -1, sizeof h); cin >> n; for (int i = 1; i <= n; i++) cin >> limit[i]; for (int i = 1; i < n; i++) { int a, b; cin >> a >> b; add(a, b), add(b, a); // 无向图 } dfs(1); int res = 0; for (int i = 0; i <= limit[1]; i++) res = (res + f[1][i]) % MOD; printf("%d\n", res); return 0; } ``` **[$51nod$ $1588$ 幸运树](https://www.cnblogs.com/littlehb/p/17203577.html)** 【树形$dp$统计树上方案数】 **[$P4084$ [$USACO17DEC$]$Barn$ $Painting$ $G$](https://www.cnblogs.com/littlehb/p/17204104.html)** **[$P3349$ [$ZJOI2016$]小星星](https://www.luogu.com.cn/problem/P3349)** TODO https://blog.csdn.net/A_Bright_CH/article/details/83443279