1. 题目
题目链接:P1352「没有上司的舞会」 。
题目描述
某大学有 n 个职员,编号为 1 ldots n。
他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。
现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 r_i,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。
所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。
输入格式
输入的第一行是一个整数 n。
第 2 到第 (n 1) 行,每行一个整数,第(i 1)行的整数表示 i号职员的快乐指数r_i。
第 (n 2)到第 2n 行,每行输入一对整数 l, k,代表 k 是 l的直接上司。
输出格式
输出一行一个整数代表最大的快乐指数。
输入输出样例
输入 #1
代码语言:javascript复制7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5
输出 #1
代码语言:javascript复制5
2. 题解
分析
这是一道简单的树上 dp问题:
- 首先构造状态 f[i][0/1] 表示以结点 i 为根的子树的最优解(第二维 0 表示 i 不参加舞会,1 表示 i参加舞会)。
- 然后构造状态转移方程:
- 上司不参加舞会时,下属可参加可不参加: f[i][0] = sum max{f[x][1], f[x][0]}
- 上司参加舞会时,下属都不会参加: f[i][1] = sum f[x][0] a_i
然后具体实现计算时:
- 可以采用 DFS 遍历整棵树,在返回上一层时更新当前结点的最优解。
- 或者可以利用树的性质,即每个非根结点有且仅有一个父结点,因此可以先计算所有子树的最优解,再往上更新父结点。最开始先计算所有叶结点,即没有孩子结点的点,我们可以把孩子结点到父结点的边看作时父结点的入边,则叶结点就是入度为 0 的点,从而得到计算的结点的顺序即为结点拓扑排序的顺序。
代码
代码语言:javascript复制#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("%dn", max(dp[u][0], dp[u][1]));
return 0;
}