1. 题目

题目链接:P1352「没有上司的舞会」

题目描述

某大学有 nn 个职员,编号为 1n1 \ldots n

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。

现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 rir_i,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。

所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入格式

输入的第一行是一个整数 nn

第 2 到第 (n+1)(n + 1) 行,每行一个整数,第 (i+1)(i+1) 行的整数表示 ii 号职员的快乐指数 rir_i

(n+2)(n + 2) 到第 2n2n 行,每行输入一对整数 l,kl, k,代表 kkll 的直接上司。

输出格式

输出一行一个整数代表最大的快乐指数。

输入输出样例

输入 #1

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

1
5

2. 题解

分析

这是一道简单的树上 dpdp 问题:

  • 首先构造状态 f[i][0/1]f[i][0/1] 表示以结点 ii 为根的子树的最优解(第二维 0 表示 ii 不参加舞会,1 表示 ii 参加舞会)。

  • 然后构造状态转移方程:

  1. 上司不参加舞会时,下属可参加可不参加:f[i][0]=maxf[x][1],f[x][0] f[i][0] = \sum max{f[x][1], f[x][0]}
  2. 上司参加舞会时,下属都不会参加:f[i][1]=f[x][0]+ai f[i][1] = \sum f[x][0] + a_i

然后具体实现计算时:

  • 可以采用 DFS 遍历整棵树,在返回上一层时更新当前结点的最优解。
  • 或者可以利用树的性质,即每个非根结点有且仅有一个父结点,因此可以先计算所有子树的最优解,再往上更新父结点。最开始先计算所有叶结点,即没有孩子结点的点,我们可以把孩子结点到父结点的边看作时父结点的入边,则叶结点就是入度为 0 的点,从而得到计算的结点的顺序即为结点拓扑排序的顺序。

代码

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <bits/stdc++.h>
#define ll int
#define MAXN 6005
using namespace std;

// 前向星存边
ll cnt;
ll head[MAXN];
struct edge{
ll to;
ll next;
} e[MAXN];
void init(ll n) {
cnt = 0;
memset(head, -1, sizeof(head));
}
void addEdge(ll u, ll v) {
e[cnt].to = v;
e[cnt].next = head[u];
head[u] = cnt++;
}

ll in[MAXN]; // 入度
ll parent[MAXN]; // 父结点
ll dp[MAXN][2];

int main()
{
ll n;
ll a[MAXN];
scanf("%d", &n);
for(ll i = 1; i <= n; ++i) {
scanf("%d", a+i);
}
ll u, v;
init(n);
for(ll i = 0; i < n-1; ++i) {
scanf("%d%d", &u, &v);
parent[u] = v; // 记录结点的父结点
addEdge(v, u); // 前向星存父结点到孩子结点的边
++in[v]; // 记录结点的度
}
// 构造拓扑序列
// 同时沿着拓扑序列更新结点的最优解
queue <ll> q;
for(ll i = 1; i <= n; ++i) {
if(!in[i]) {
dp[i][1] = a[i];
--in[parent[i]];
if(!in[parent[i]]) {
q.push(parent[i]);
}
}
}
while(q.size()) {
u = q.front();
q.pop();
--in[parent[u]];
if(!in[parent[u]]) q.push(parent[u]);
dp[u][0] = dp[u][1] = 0;
for(ll i = head[u]; i != -1; i = e[i].next) {
dp[u][0] += max(dp[e[i].to][0], dp[e[i].to][1]);
dp[u][1] += dp[e[i].to][0];
}
dp[u][1] += a[u];
}
printf("%d\n", max(dp[u][0], dp[u][1]));
return 0;
}