树形 DP

在学习本章前请确认你已经学习了 动态规划部分简介

树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形 DP 一般都是递归进行的。

例题

以下面这道题为例,介绍一下树形 DP 的一般过程。

例题洛谷 P1352 没有上司的舞会

某大学有 n 个职员,编号为 1\text{~} N 。他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 a_i ,但是呢,如果某个职员的上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

我们可以定义 f(i,0/1) 代表以 i 为根的子树的最优解(第二维的值为 0 代表 i 不参加舞会的情况,1 代表 i 参加舞会的情况)。

显然,我们可以推出下面两个状态转移方程(其中下面的 x 都是 i 的儿子):

  • f(i,0) = \sum\max \{f(x,1),f(x,0)\} (上司不参加舞会时,下属可以参加,也可以不参加)
  • f(i,1) = \sum{f(x,0)} + a_i (上司参加舞会时,下属都不会参加)

我们可以通过 DFS,在返回上一层时更新当前节点的最优解。

代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <algorithm>
#include <cstdio>
using namespace std;
struct edge {
  int v, next;
} e[6005];
int head[6005], n, cnt, f[6005][2], ans, is_h[6005], vis[6005];
void addedge(int u, int v) {
  e[++cnt].v = v;
  e[cnt].next = head[u];
  head[u] = cnt;
}
void calc(int k) {
  vis[k] = 1;
  for (int i = head[k]; i; i = e[i].next)  //枚举该节点的每个子节点
  {
    if (vis[e[i].v]) continue;
    calc(e[i].v);
    f[k][1] += f[e[i].v][0];
    f[k][0] += max(f[e[i].v][0], f[e[i].v][1]);
  }
  return;
}
int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) scanf("%d", &f[i][1]);
  for (int i = 1; i < n; i++) {
    int l, k;
    scanf("%d%d", &l, &k);
    is_h[l] = 1;
    addedge(k, l);
  }
  for (int i = 1; i <= n; i++)
    if (!is_h[i])  //从根节点开始DFS
    {
      calc(i);
      printf("%d", max(f[i][1], f[i][0]));
      return 0;
    }
}

习题

HDU 2196 Computer

POJ 1463 Strategic game

POJ 3585 Accumulation Degree


评论