Files
python/TangDou/AcWing/DP/Tree/树形DP【专题】.md
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

160 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

##树形$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 <bits/stdc++.h>
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