树形DP

以下当前点为\(u\)\(v\)\(u\)的儿子

为什么树上可以用DP?

每棵树代表着一个大问题,而其中的子树就是子问题

统一思想

每个点/边/子树代表着一个问题的解,先把子树的解求出以求出包含该子树的子树的解。

对于以点为对象的DP

最大独立集

模版问题描述(仅类似)

在一棵树上有若干个点,而每个点有一个权值,求选出若干个不相邻的点的权值的最大和。

解决方式

状态定义

$ Dp_{u,0/1} $ 表示当前结点u不选/选的子树的最大和。

状态转移

当该结点被选了时,那么就不选他的儿子,即

\[\text{dp}_{u,1}=val_u\;+\sum_{v\in son_u}\text{dp}_{v,0} \]

当该结点被没被选了时,那么就取选/不选中的最大值

\[\text{dp}_{u,1}=\sum_{v\in son_u}max(\text{dp}_{v,0},\text{dp}_{v,1}) \]

代码实现

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,0}=1+\sum_{v\in son_u}min(\text{dp}_{v,0},\text{dp}_{v,1},\text{dp}_{v,2}) \]

\[\text{dp}_{u,2}=1+\sum_{v\in son_u}min(\text{dp}_{v,0},\text{dp}_{v,1}) \]

$ \text{dp}_{u,1} $ 相对难想,需要在计算完其余两个时再求,但也可以就一起求。
在循环时同时求一个最小值\(minx\)

\[minx = min(-min(\text{dp}_{v,0},\text{dp}_{v,1}) + \text{dp}_{v,0}) \]

在最后可得

\[\text{dp}_{u,1}=\text{dp}_{u,2}+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\)不放置/放置士兵。

状态转移

\[\text{dp}_{u,0} = \sum_{v \in son_u}\text{dp}_{v,1} \]

\[\text{dp}_{u,1} = 1 + \sum_{v \in son_u}min(\text{dp}_{v,0},\text{dp}_{v,1}) \]

代码实现

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\)组组队意向,请求出保证满足以下条件的组队方式:

  1. 一个人只能待在一个队伍
  2. 只能根据组队意向组队

解决方式

挺好想的,自己先想想。

实现代码

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);
	}
}

总结

还是挺简单的。_

posted @ 2026-02-02 19:41  cqbzcdr  阅读(5)  评论(0)    收藏  举报