Files
python/TangDou/AcWing/DP/Tree/树形DP【专题】.md

160 lines
4.8 KiB
Markdown
Raw Normal View History

2025-08-30 18:35:01 +08:00
##树形$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