树形 DP 入门

WHAT

本身是靠递归定义的,这对于 dp 来说十分滴友好(本身就具有递归结构)

Problem Expressions

Independent Set (Maximum)

TO vertexes

define

对于任意一个图 \(G\) ,确定一个边集 \(V'\) ,使得其中的点两两不相邻

co-NP

Dominating Set (Minimum)

TO vertexes

definition

每个点的覆盖范围一定,在图 \(G\) 中选出一个点集 \(V'\) ,使得 \(G\) 中的每个点都被覆盖

co-NP

Vertex Cover (Minimum)

TO vertexes

definition

每个点可覆盖所有相邻的边,在图 \(G\) 中选出一个点集 \(V'\) ,使得 \(G\) 中的每条边都被覆盖

co-NP

Matching (Maximum)

TO edges

definition

在图 \(G\) 中选出一个点集 \(E'\) ,使得这些边两两不共点

NP-hard

Addition

在特殊条件限制下(例如树、二分图),以上四个问题可以在多项式时间内解决



Problem Solutions

Solution 1st

定义 dp[u][0/1] 表示节点 u 选没选

则很容易得出来以下转移式:

\[dp_{u,1} = w_u + \sum_{v \in \text{son}(u)} dp_{u,0}\\ dp_{u,0} = \sum_{v \in \text{son}(u)} \max(dp_{v,0}, dp_{v,1}) \]

View Code
void DFS(int u, int father) {
	dp[u][0] = 0;
	dp[u][1] = wealth[u];
	for (int v : adj[u]) {
		if (v == father) continue;
		DFS(v, u);
		dp[u][0] += max(dp[v][0], dp[v][1]);
		dp[u][1] += dp[v][0];
	}
}

Solution 2nd

definition

dp[u][0] :被自己覆盖

dp[u][1]:被子节点覆盖

dp[u][2]:被父节点覆盖

State Transformation Function

\[dp_{u,0} = \sum_{v \in \text{son}(u)} \min_{i = 0}^2 dp_{v,i}\\ dp_{u,2} = \sum_{v \in \text{son}(u)} \min_{i = 0}^1 dp_{v,i}\\ dp_{u,1} = \min_{v \in \text{son}(u)} (dp_{u,2}-\min(dp_{v,0},dp_{v,1})+dp_{v,0} \]

View Code
void DFS(int u, int father) {
	dp[u][0] = 1;
	for (int v : adj[u]) {
		if (v == father) 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]);
	}

	dp[u][1] = INT_MAX / 3;
	for (int v : adj[u]) {
		if (v == father) continue;
		dp[u][1] = min(dp[u][1], dp[u][2] - min(dp[v][0], dp[v][1]) + dp[v][0]);
	}
}
提醒

最后统计答案的时候,由于根节点并没有父亲节点
所以 ans = min(dp[root][0], dp[root][1])

Solution 3rd

稍微转化一下就变得和第一个问题类似

View Code
void DFS(int u, int father) {
	dp[u][0] = 0;
	dp[u][1] = wealth[u];
	for (int v : adj[u]) {
		if (v == father) continue;
		DFS(v, u);
		dp[u][0] += dp[v][1];
		dp[u][1] += min(dp[v][0], dp[v][1]);
	}
}

Solution 4th

我们会惊奇的发现,思路竟然与第二个问题相同

\[dp_{u,0} = \sum_{v \in \text{son}(u)} \max(dp_{v,0}, dp_{v,1})\\ dp_{u,1} = \max_{v \in \text{son}(u)} \{ dp_{u,0} - \max(dp_{v,0}, dp{v,1]} + dp_{v,0} + 1 \} \]

View Code
void DFS(int u, int father) {
	dp[u][0] = 0;
	for (int v : adj[u]) {
		if (v == father) continue;
		DFS(v, u);
		dp[u][0] += max({dp[v][0], dp[v][1]});
	}

	dp[u][1] = INT_MIN / 3;
	for (int v : adj[u]) {
		if (v == father) continue;
		dp[u][1] = max(dp[u][1], dp[u][0] - max(dp[v][0], dp[v][1]) + dp[v][0] + 1);
	}
}

树的直径

搜索

前置知识

从树上任意一个节点出发,记与其距离最远的点为 \(s\)\(s\) 出发搜到的与之最远的点为 \(t\) ,路径 \(s \to t\) 即为树的直径

Proof

\(\blacksquare\)

图1

  • \(u\) 在直径 \(s \to t\) 上时

假设从 \(s\) 搜到的点并不是 \(t\),记为 \(T\)

\[\ \ \ \ \ \ \ \ \ \ \ \text{dist}(u, t) \le \text{dist}(u, T)\\ \implies \text{dist}(s,t) \le \text{dist}(s,T) \]

\(s\to t\) 是直径的事实矛盾

  • \(u\) 不在直径上,但是有交点

    同理

\(\square\)

DP

观察一条直径 \(s \to t\)

在其上选择一个点 \(u\)

我们会发现 \(u\to s\)\(u\to t\) 一定是从 \(u\) 出发的最长与次长路径

不放定义 \(dp_{\text{type},u}\) 表示从节点 \(u\) 出发的最长/次长路径长度

\(v \in \text{son}(u)\)\(\text{length} = dp_{1,v}+w_{uv}\)

则有:

  • \(\text{length} > dp_{1,u}\) 时:

    \[dp_{2,u}=dp_{1,u}\\ dp_{1,u}=\text{length} \]

  • \(dp_{1,u}>\text{length}>dp_{2,u}\) 时:

    \[dp_{2,u} = \text{length} \]

    代码实现就是:

    if (length > dp[1][u]) swap(length, dp[1][u]);
    if (length > dp[2][u]) swap(length, dp[2][u]);
    
posted @ 2026-02-02 19:08  Yangyihao  阅读(4)  评论(0)    收藏  举报