树型 d p dp dp 即在树上进行 d p dp dp 。
树是无环图,顺序可以是从叶子到根节点,也可以从根到叶子节点。
一般树型 d p dp dp 的特征很明显,即状态可以表示为树中的节点,每个节点的状态可以由其子节点状态转移而来(从叶子到根的顺序),或是由其父亲节点转移而来(从根到叶节点的顺序),也可是两者结合。
找出状态和状态转移方程仍然是树型 d p dp dp 的关键。
例题:蓝桥舞会
思路:
假设题目样例也变化为左图,那么便有又图的结果:
1.首先确定 d p dp dp 数组以及数组每一维表示的意义。 d p [ i ] [ j ] dp[ i ][ j ] dp[i][j] 表示以 i i i 为根节点的子树, j j j 的取值只有 0 0 0 和 1 1 1 , 1 1 1 表示选了节点 i i i , 0 0 0 表示没选节点 i i i 。然后确定状态的转移,当选了 i i i 时,根据题目要求不能选择 i i i 的直接子节点,当没有选择 i i i 时,可以选择与 i i i 直接相连的子节点,当然,也可以不选。那么转移公式如下:
2. d p [ i ] [ 0 ] + = m a x ( d p [ u ] [ 0 ] , d p [ u ] [ 1 ] ) dp[ i ][ 0 ] += max(dp[ u ][ 0 ] , dp[ u ][ 1 ]) dp[i][0]+=max(dp[u][0],dp[u][1]) 其中 u u u 是与 i i i 直接相连的子节点,对于该子节点,我既可以选,也可以不选,取两种状态的最大值。
3. d p [ i ] [ 1 ] + = d p [ u ] [ 0 ] dp[ i ][ 1 ] += dp[ u ][ 0 ] dp[i][1]+=dp[u][0] 其中 u u u 是与 i i i 直接相连的子节点,对于该子节点,必然不能选择,只有一种状态。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
public class Main {
static ArrayList<Integer>[] q;//存储结点i的所有子结点
static boolean[] visit;//判断此点是否是根节点
static long[][] dp;
static int[] a;//记录每一个人的快乐指数
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(in.readLine());
a = new int[n + 1];
q = new ArrayList[n+1];
dp = new long[n + 1][2];
visit = new boolean[n + 1];
String[] s = in.readLine().split(" ");
for(int i=1;i<=n;i++) {
a[i] = Integer.parseInt(s[i-1]);
q[i] = new ArrayList<>();
}
for(int i=1;i<n;i++) {
s = in.readLine().split(" ");//u的上司是v
int u = Integer.parseInt(s[0]);
int v = Integer.parseInt(s[1]);
visit[u] = true;//u不是根结点
q[v].add(u);//u是v的一个子结点(下属)
}
int root = -1;
for(int i=1;i<=n;i++) {
if(!visit[i]) {//只要根结点的visit值仍为false
root = i;
break;
}
}
dfs(root);
System.out.println(Math.max(dp[root][1], dp[root][0]));//最终结果考虑根结点和不考虑根结点的最大值
}
static void dfs(int root) {
dp[root][1] += a[root];//考虑根结点的初值为根结点的快乐指数
for (int i = 0; i < q[root].size(); i++) {//处理root的每一个子结点
int to = q[root].get(i);
dfs(to);
dp[root][1] += dp[to][0];//考虑根结点,那么不能考虑子结点
dp[root][0] += Math.max(dp[to][0], dp[to][1]);//不考虑根结点,子结点可以考虑,也可以不考虑
}
}
}