来源 AcWing

题目

Ural大学有N名职员,编号为1~N。

他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。

每个职员有一个快乐指数,用整数 Hi 给出,其中 1 ≤ i ≤ N。

现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。

在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。

输入格式

第一行一个整数N。

接下来N行,第 i 行表示 i 号职员的快乐指数Hi

接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。

输出格式

输出最大的快乐指数。

数据范围

1 ≤ N ≤ 6000
−128 ≤ Hi ≤ 127

输入样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

输出样例

1
5

分析

使用树形DP

状态表示:

  • f[u][0]:所有以 u 为根的子树中选,且不选 u 的方案集合
  • f[u][1]:所有以 u 为根的子树中选,且选 u 的方案集合

状态转移:

  • 如果根节点不选,那么子节点(j)可选可不选

    f[u][0] = ∑max(f[j][1], f[j][0])

  • 如果根节点选,那么子节点(j)不能选

    f[u][1] = ∑f[j][0]

代码

算法1

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
41
42
#include<bits/stdc++.h>
using namespace std;

const int N = 6010;
int e[N], h[N], ne[N], idx, ha[N], f[N][2]; // ha为高兴值
bool fa[N];

void add(int a, int b) { // 添加边 a -> b
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs(int u) { // 递归遍历
f[u][1] = ha[u];
for(int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
dfs(j);
f[u][1] += f[j][0];
f[u][0] += max(f[j][1], f[j][0]);
}
}

int main() {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++)
scanf("%d", &ha[i]);
memset(h, -1, sizeof h);
for(int i = 0; i < n - 1; i++) {
int a, b;
scanf("%d%d", &a, &b);
fa[a] = true;
add(b, a);
}

int root = 1; // 找根节点
while(fa[root]) root++;

dfs(root);

printf("%d", max(f[root][0], f[root][1]));
return 0;
}

算法2

大佬的解法:https://www.acwing.com/solution/acwing/content/4757/

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
#include <iostream>
#include <cstdio>
using namespace std;
int dp[2][6010];
int f[2][6010];//f[0]为父亲,f[1]为高兴值
int ind[6010];//入度
int vis[6010];//访问标记
int root;//树的根
void dfs(int u){//递归从后往前更新
if(!u) return;
vis[u]=1;//已访问
root=u;//最后一个访问到的一定是根,所以一直更新根就行了
dp[0][f[0][u]]+=max(dp[1][u]+f[1][u],dp[0][u]);//给父亲更新
dp[1][f[0][u]]+=dp[0][u];
ind[f[0][u]]--;//更新完一个子节点
if(!ind[f[0][u]]) dfs(f[0][u]);//在所有子节点更新后再更新(入度为0)
}
int main(){
int n=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&f[1][i]);
int a,b;
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
f[0][a]=b;//保存节点信息
ind[b]++;
}
for(int i=1;i<=n;i++)
if(!vis[i]&&!ind[i])//没有被访问过,没有入度,说明是叶子节点
dfs(i);
printf("%d\n",max(dp[0][root],dp[1][root]+f[1][root]));//取根节点两种方案的最大值
return 0;
}