树形 DP & 换根 DP 笔记
一、树形 DP
1. 用途
树形 DP 与普通 DP 的区别,就是普通的 DP 通常是在一个一维数组或二维数组上 DP,但树形 DP 是在一棵树上 DP,解决一些与树形结构有关的问题。
2. 实现方法
(1). 建图
使用链式前向星存图。
(2). 不同用途不同方法
①. 选或不选类
先放一个例题:
| 平台 | 网址 |
|---|---|
| 洛谷 | https://www.luogu.com.cn/problem/P1352 |
不难看出,上司与下属的关系网是树形结构的,因此,选用树形 DP 解决这道题。
用
由于树形 DP 通常由子节点状态转移得到父节点状态,因此状态转移方程左侧为
树形 DP 部分代码如下:
void dp(int x){
f[x][0]=0;
f[x][1]=h[x];
for(int i=head[x];i;i=edge[i].next){
int t=edge[i].v;
dp(t);
f[x][0]+=max(f[t][0],f[t][1]);
f[x][1]+=f[t][0];
}
}②. 求权值总和/子树大小类
此类树形 DP 多数是前置需要的,难度并不是很高。若用
③.求树的直径类
树的直径,指一棵树里任意两点的最远距离。
如下图所示,这棵树每条路径的长度仍未知,
树形 DP 部分的代码如下
inline int dp(int u,int fa){
int d1=0,d2=0;
for(int i=head[u];i;i=edge[i].next){
int j=edge[i].v;
if(j==fa)continue;
int t=dp(j,u)+edge[i].len;
if(t>d1)d2=d1,d1=t;
else if(t>d2)d2=t;
}
ans=max(ans,d1+d2);
return d1;
}二、换根 DP
1. 用途
换根 DP 由树形 DP 的基础而来,换根 DP 用于解决要枚举一棵树中多个点作为根节点的题,如果进行
2. 实现方法
(1). 思路
换根 DP 的思路,一般都是先用一个
(2). 做法
以经典的“求经过每个节点的最长链”的长度为例。
首先任选一点为根,处理出以这个点为根时,从每个点
void dfs1(int u, int fa) {
for (auto [v, w] : g[u]) {
if (v == fa)
continue;
dfs1(v, u);
int dv1 = dp1[v] + w;
if (dp1[u] <= dv1) {
dp2[u] = max(dp1[u], dp2[v] + 2 * w);
dp1[u] = dv1;
pos[u] = v;
} else
dp2[u] = max(dp2[u], dv1);
}
}然后考虑换根 dp,经过点
- 由
和 拼接而成。 - 由
和往 的父亲走的一条路径拼接而成。
所以,这个问题中,换根 dp 要求的就是往
是一开始选择的根: 。 可以沿着父亲再往父亲走:
。 的路径是 的一部分: 。因为肯定不能经过 再走回 这条路径。 其他情况:
。
void dfs2(int u, int fa, int fw) {
if (u == 1)
up[u] = c[u];
else {
up[u] = max(up[fa] + fw, c[u]);
if (pos[fa] == u) // pos[fa] 就是 fa 的 d1 链指向的第一个节点
up[u] = max(up[u], dp2[fa] + fw);
else
up[u] = max(up[u], dp1[fa] + fw);
}
for (auto [v, w] : g[u]) {
if (v == fa)
continue;
dfs2(v, u, w);
}
}结合代码也是比较好理解。
三、树上背包的时间复杂度
就是在树上做背包,状态形如
一眼看上去
证明咕了先。