1. 程式人生 > 其它 >DP專題-學習筆記:樹形 DP

DP專題-學習筆記:樹形 DP

目錄

1. 前言

樹形 DP,是一種 DP (廢話),專門用於樹上的 DP。

這類 DP 因為其板子好記,標記顯眼而十分易懂。

而且樹形 DP 長得就不像 DP,更像暴力搜尋。

當然 DP 肯定也有很大思維量的,但是像樹形 DP 程式碼確實挺好打。

2. 詳解

例題:P1352 沒有上司的舞會

題目實際上就是給出一棵有 \(n\) 個點的樹,選出一些點,使得這些點兩兩不相鄰,求最大點權和。

這就是樹形 DP 的板子題。

首先先把樹存下來。

前言裡面說過:樹形 DP 更像一種暴力搜尋,於是我們需要對這棵樹做一遍 dfs,邊 dfs 邊 DP。

考慮到樹形 DP 是一種 DP (難道不是嗎)

,所以我們仍然需要 DP 的基本套路:設狀態,推轉移方程,推初值,求答案。

以下的所有討論都假設已經確定樹的形態以及根。

\(f_{k,0/1}\) 表示節點 \(k\) 以及其子樹選出點的最大權值和。\(0\) 表示節點 \(k\) 不放,\(1\) 表示節點 \(k\) 放。

那麼作為樹形 DP,很重要的一點就是:父節點的 \(f\) 的計算是與子節點有關係的。

因此對於這道題,我們的狀態轉移方程如下:

\[f_{k,0}=\max\{f_{u,0},f_{u,1} | u \in V\} \] \[f_{k,1}=r_k+\max\{f_{u,0} | u \in V\} \]

其中 \(V\)

\(k\) 的兒子組成的集合。

如果這個點不選,那麼兒子可選可不選;但是如果選了,那麼兒子不能選。

注意:選出的點集 \(U=\varnothing\) 也是可以的,因此可能答案為 \(0\)

最後的答案為 \(f_{root,0/1}\) 中的最大值。

程式碼:

#include <bits/stdc++.h>
#define Max(a, b) ((a > b) ? a : b)
using namespace std;

typedef long long LL;
const int MAXN = 6e3 + 10;
int n, r[MAXN], f[MAXN][2], root;
vector <int> Next[MAXN];
bool book[MAXN];

int read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
	return sum * fh;
}

void dfs(int now, int fa)
{
	f[now][0] = 0, f[now][1] = r[now];
	for (int i = 0; i < Next[now].size(); ++i)
	{
		int u = Next[now][i];
		if (u == fa) continue;
		dfs(u, now);
		f[now][0] = Max(f[now][0], Max(f[now][0] + f[u][0], f[now][0] + f[u][1]));
		f[now][1] = Max(f[now][1], f[now][1] + f[u][0]);
	}
}

int main()
{
	n = read();
	for (int i = 1; i <= n; ++i) r[i] = read();
	for (int i = 1; i < n; ++i)
	{
		int x = read(), y = read(); book[x] = 1;
		Next[y].push_back(x); Next[x].push_back(y);
	}
	for (int i = 1; i <= n; ++i)
		if (!book[i]) {root = i; break;}
	dfs(root, root);
	printf("%d\n", Max(f[root][0], f[root][1]));
	return 0;
}

這裡提供一種樹形 DP 的板子,但是不能保證這個板子能夠通過所有題目。

int f[...];

void dfs(int now, int fa...)
{
	/*設定初值*/
	for (/*遍歷兒子*/)
	{
		int u = /*兒子*/;
		if (u == fa) continue;//注意不能返回父親節點
		dfs(u, now...);
		/*轉移*/
	}
}

3. 練習題

練習題傳送門:DP演算法總結&專題訓練2(樹形 DP)