把人事关系看成一棵树,每个节点记录选与不选两种最优值,自底向上合并,O(N) 求最大欢乐度。
OJ: roj
题目 ID: 1582
难度:普及-
标签:树形DP动态规划树
创建: 2026-09-30 19:59
更新: 2026-10-06 00:48
题目描述
学校职员构成一棵以校长为根的人事关系树,编号 L K 表示 0 0 结束;输出最大欢乐度。样例输入 7 / 1 / 1 / 1 / 1 / 1 / 1 / 1 / 1 3 / 2 3 / 6 4 / 7 4 / 4 5 / 3 5 / 0 0,输出 5。数据范围
思路
树形 DP:每个节点保留两个值,dp0[u] 为 dp1[u] 为 dp0[u]=Σmax(dp0[v],dp1[v])、dp1[u]=p[u]+Σdp0[v](max(dp0[root], dp1[root])。
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 20:01
# update_at: 2026-09-30 20:01
import sys
from collections import defaultdict
def tree_dp(children: dict[int, list[int]], joy: list[int], root: int) -> int:
"""树形 DP:相邻两人不能同时选,返回整棵树的最大欢乐度。"""
# 迭代代替递归:先取"父先于子"的遍历序,再倒序处理,孩子必然先于父亲算完
order = [root]
for boss in order: # 遍历中列表不断变长,恰好把整棵树层展成一列
order += children[boss]
dp0 = [0] * len(joy) # dp0[u]:u 不参加时,u 子树的最优欢乐度
dp1 = joy[:] # dp1[u]:u 参加时先记下 u 自己的欢乐度
for boss in reversed(order):
for emp in children[boss]:
dp0[boss] += max(dp0[emp], dp1[emp]) # 上司不来,下属随意
dp1[boss] += dp0[emp] # 上司来了,直接下属都不能来
return max(dp0[root], dp1[root]) # 根来或不来取大;都不来即空名单,值为 0
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
joy = [0] + [next(data) for _ in range(n)] # 下标 1..n 对齐员工编号
# 关系行 "L K":第 K 人是第 L 人的直接上司;0 0 结束
children: dict[int, list[int]] = defaultdict(list)
subordinate = set()
while True:
emp, boss = next(data), next(data)
if emp == 0 and boss == 0:
break
children[boss].append(emp)
subordinate.add(emp)
# 树根 = 从没当过下属的人
root = next(u for u in range(1, n + 1) if u not in subordinate)
print(tree_dp(children, joy, root))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 00:48
* update_at: 2026-10-06 00:48
*/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll MAXN = 6005; // 职员数上界 6000,多开一点
ll n; // 职员总数
ll joy[MAXN]; // joy[i]:第 i 个职员的欢乐度
vector<ll> children[MAXN]; // children[u]:u 的直接下属
bool has_boss[MAXN]; // has_boss[u]:u 是否有直接上司,用来找树根
ll dp0[MAXN]; // dp0[u]:u 不参加时,u 子树的最大欢乐度
ll dp1[MAXN]; // dp1[u]:u 参加时,u 子树的最大欢乐度
ll order[MAXN]; // order[]:父先于子的遍历序
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> joy[i];
}
// 关系行 "L K":K 是 L 的直接上司,输入以 0 0 结束
ll emp = 0;
ll boss = 0;
while (cin >> emp >> boss) {
if (emp == 0 && boss == 0) {
break;
}
children[boss].push_back(emp);
has_boss[emp] = true;
}
// 树根就是从没当过下属的人
ll root = 1;
for (ll u = 1; u <= n; u++) {
if (!has_boss[u]) {
root = u;
break;
}
}
// 先做一遍父先于子的遍历,得到 order[]
ll head = 0;
ll tail = 0;
order[tail++] = root;
while (head < tail) {
ll u = order[head++];
ll cnt = children[u].size();
for (ll i = 0; i < cnt; i++) {
order[tail++] = children[u][i];
}
}
// 倒序遍历即后序:孩子必然先于父亲算完
for (ll i = n - 1; i >= 0; i--) {
ll u = order[i];
dp1[u] = joy[u];
ll cnt = children[u].size();
for (ll j = 0; j < cnt; j++) {
ll v = children[u][j];
dp0[u] += max(dp0[v], dp1[v]); // u 不来,下属随意
dp1[u] += dp0[v]; // u 来了,直接下属都不能来
}
}
// 根来或不来取大;都不选即空名单,值为 0
cout << max(dp0[root], dp1[root]) << '\n';
return 0;
}