160 lines
4.8 KiB
Markdown
160 lines
4.8 KiB
Markdown
|
|
##树形$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
|