树形DP
以下当前点为\(u\),\(v\)为\(u\)的儿子
为什么树上可以用DP?
每棵树代表着一个大问题,而其中的子树就是子问题。
统一思想
每个点/边/子树代表着一个问题的解,先把子树的解求出以求出包含该子树的子树的解。
对于以点为对象的DP
最大独立集
模版问题描述(仅类似)
在一棵树上有若干个点,而每个点有一个权值,求选出若干个不相邻的点的权值的最大和。
解决方式
状态定义
$ Dp_{u,0/1} $ 表示当前结点u不选/选的子树的最大和。
状态转移
当该结点被选了时,那么就不选他的儿子,即
当该结点被没被选了时,那么就取选/不选中的最大值
代码实现
void dfs(int u) {
dp[u][0] = 0, dp[u][1] = val[u];
for (auto v : g[u]) {
dfs(v);
dp[u][0] += max(dp[v][0], dp[v][1]), dp[u][1] += dp[v][0];
}
}
最小支配集(覆盖与被覆盖)
DP做法
问题描述
每个顶点可以放置守卫,保护自己及相邻的节点。求最少需要选择多少个顶点放置守卫,使得树上所有节点都被保护。
解决方式
状态定义
\(\text{dp}_{u,0}\) 表示\(u\)被选中(\(u\)可以覆盖自己及子节点)。
\(\text{dp}_{u,1}\) 表示\(u\)被子节点保护(\(u\)没选,但至少有一个儿子被选了)。
\(\text{dp}_{u,2}\) 表示\(u\)被父节点保护(\(u\)没选,等父节点来保护)。
状态转移
$ \text{dp}_{u,1} $ 相对难想,需要在计算完其余两个时再求,但也可以就一起求。
在循环时同时求一个最小值\(minx\)
在最后可得
代码实现
void dfs(int u, int fa) {
dp[u][0] = 1;
int minx = INF;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
dp[u][0] += min({dp[v][0], dp[v][1], dp[v][2]});
dp[u][2] += min({dp[v][0], dp[v][1]});
minx = min(-min(dp[v][0], dp[v][1]) + dp[v][0], minx);
}
dp[u][1] = dp[u][2] + minx;
}
贪心做法
每个点要么自己保护自己,要么被别人保护。(相当于该点一定要被保护)
那么,对于 深度最深(叶子结点) 的点来说,可以保护他的有两个情况: 自己,自己的父亲。
贪心的考虑,显然选择父节点要更好一些。
- 每次选择深度最深的点,放在他的父亲。
- 如果不存在父亲,那么就放在自己身上。
贪心的缺陷
贪心只能解决每个点的代价相同的问题,不能解决每个点的代价不同的问题。
对于以边为对象的DP
最小点覆盖
形象化题意
给定一棵无根树,士兵驻扎在点上,驻扎在 x 点的士兵可以看守所有与 x 相邻的边,
请问守住所有的边至少需要多少士兵?
解决方式
状态定义
\(\text{dp}_{u,0/1}\) 表示分别以\(u\)为根的子树在点\(v\)不放置/放置士兵。
状态转移
代码实现
void dfs(int u, int fa) {
dp[u][1] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
dp[u][0] += dp[v][1], dp[u][1] += min(dp[v][0], dp[v][1]);
}
}
最大匹配
形象化题面
\(n\)个人组队打比赛,每队两个人,一共给了\(n-1\)组组队意向,请求出保证满足以下条件的组队方式:
- 一个人只能待在一个队伍
- 只能根据组队意向组队
解决方式
挺好想的,自己先想想。
实现代码
void dfs(int u, int fa) {
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
dp[u][0] += max(dp[v][0], dp[v][1]);
}
for (auto v : g[u]) {
if (v == fa) continue;
dp[u][1] = max(dp[u][1], dp[u][0] - max(dp[v][0], dp[v][1]) + dp[v][0] + 1);
}
}
总结
还是挺简单的。_

浙公网安备 33010602011771号