DP專題-學習筆記:樹形 DP
阿新 • • 發佈:2022-04-08
目錄
,所以我們仍然需要 DP 的基本套路:設狀態,推轉移方程,推初值,求答案。
為 \(k\) 的兒子組成的集合。
1. 前言
樹形 DP,是一種 DP (廢話),專門用於樹上的 DP。
這類 DP 因為其板子好記,標記顯眼而十分易懂。
而且樹形 DP 長得就不像 DP,更像暴力搜尋。
當然 DP 肯定也有很大思維量的,但是像樹形 DP 程式碼確實挺好打。
2. 詳解
題目實際上就是給出一棵有 \(n\) 個點的樹,選出一些點,使得這些點兩兩不相鄰,求最大點權和。
這就是樹形 DP 的板子題。
首先先把樹存下來。
前言裡面說過:樹形 DP 更像一種暴力搜尋,於是我們需要對這棵樹做一遍 dfs,邊 dfs 邊 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\)
如果這個點不選,那麼兒子可選可不選;但是如果選了,那麼兒子不能選。
注意:選出的點集 \(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)