目录
题目:
没有上司的舞会 (nowcoder.com)https://ac.nowcoder.com/acm/problem/51178
Question
题号:NC51178
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 32768K,其他语言65536K
64bit IO Format: %lld
题目描述
Ural大学有N名职员,编号为1~N。
他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。
每个职员有一个快乐指数,用整数 HiHi 给出,其中 1≤i≤N,1≤i≤N1\leq i\leq N,1\leq i\leq N1≤i≤N,1≤i≤N。
现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。
在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。
输入描述:
第一行一个整数N。 接下来N行,第 i 行表示 i 号职员的快乐指数Hi。 接下来N-1行,每行输入一对整数L, K,表示K是L的直接上司。 最后一行输入0,0。
输出描述:
输出最大的快乐指数。
示例1
输入
7 1 1 1 1 1 1 1 1 3 2 3 6 4 7 4 4 5 3 5 0 0
输出
5
备注:
1≤N≤6000, −128≤Hi≤127
题目解析:
所谓的树形DP就是基于树这个数据结构进行的动态规划,作为入门题目,我们首先要思路清晰地去思考这个问题,我们需要哪几个部分去做这个工作呢,第一个树这个数据结构我们要掌握,也就是我们首先根据题目的意思我们要建立一个具有上下级关系的树,第二我们怎么在树上进行动态规划呢,其实很简单,我们需要考虑动态规划的构成,最优子结构和无后效性,在该题目上其实都满足了,我们可以开始从root节点开始考虑问题,
假设我们选了root,所求的最大值就是左边子树不选时的最大值加上右边子树不选时的最大值最后加上root的权值;
假设我们不选root,所求最大值就是max(左边子树选,左边子树不选)+max(右边子树选,右边子树不选);问题也被继续拆分下去直至叶子节点,所有节点的最大值都可以被更新出来。
第一步建立树:
// c++ 代码示例
#include <iostream>
#include <algorithm>
using namespace std ;
// 基于题目数据量
const int N = 6e3 + 10 ;
// last 数组存储每个节点的最后一条边的编号
int last[N] ;
// ne 数组存储每条边的前一条边的编号
int ne[N] ;
// edge 数组存储每条边的终点
int edge[N] ;
// 存储每个节点的权值
int a[N] ;
// 计数器,记录边的数量
int cnt = 1 ;
// 标记有没有父节点
bool pre[N] ;
// x是父节点,y是子节点
void add(int x, int y)
{
edge[cnt] = y ;
ne[cnt] = last[x] ;
last[x] = cnt++ ;
// 就是1的终边为2,最后一条边的编号也是2
// x -> y
// 1 -> 2
// (x = 1, y = 2)
// edge[1] = 2
// ne[1] = last[1]
// last[1] = 2
}
int main()
{
int n ;
cin >> n ;
for (int i = 1 ; i <= n ; i++)
{
cin >> a[i] ;
}
int x, y ;
for (int i = 1 ; i <= n ; i++)
{
cin >> x >> y ;
add(y, x) ;
}
}
第二步树上DP操作:
假设我们选了root,所求的最大值就是左边子树不选时的最大值加上右边子树不选时的最大值最后加上root的权值;
假设我们不选root,所求最大值就是max(左边子树选,左边子树不选)+max(右边子树选,右边子树不选);问题也被继续拆分下去直至叶子节点,所有节点的最大值都可以被更新出来。
// c++ 代码示例
#include<iostream>
#include<math.h>
#include<string.h>
using namespace std;
const int N=6000+10;
int last[N];
int ne[N],edge[N],cnt=1;
bool pre[N];
int dp[N][2];
int a[N];
void add(int a, int b){
edge[cnt] = b;
ne[cnt] = last[a];
last[a] = cnt++;
}
void dps(int root)
{
dp[root][0]=0;//不选root号节点;
dp[root][1]=a[root];//选root号节点
for(int i=last[root];i>=1;i=ne[i])
{
int j=edge[i];
dps(j);
dp[root][0]+=max(dp[j][0],dp[j][1]);
dp[root][1]+=dp[j][0];
}
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
//因为0包含的有边
int x,y;
for(int i=1;i<n;i++)//n-1条边
{cin>>x>>y;//y是x的直接上司
add(y,x);
pre[x]=true;//a有父节点b
}
int v=1;
while(pre[v])v++;//找到没有父节点的点
dps(v);
cout<<max(dp[v][1],dp[v][0]);
}
# python 代码示例
import sys
sys.setrecursionlimit(10000)
N = 6000 + 10
last = [-1] * N
ne = [-1] * N
edge = [-1] * N
cnt = 1
pre = [False] * N
dp = [[0, 0] for _ in range(N)]
a = [0] * N
def add(a, b):
global cnt
edge[cnt] = b
ne[cnt] = last[a]
last[a] = cnt
cnt += 1
def dps(root):
dp[root][0] = 0 # 不选root号节点;
dp[root][1] = a[root] # 选root号节点
i = last[root]
while i != -1:
j = edge[i]
dps(j)
dp[root][0] += max(dp[j][0], dp[j][1])
dp[root][1] += dp[j][0]
i = ne[i]
if __name__ == "__main__":
n = int(input())
for i in range(1, n + 1):
a[i] = int(input())
for i in range(1, n):
x, y = map(int, input().split()) # y是x的直接上司
add(y, x)
pre[x] = True # a有父节点b
v = 1
while pre[v]:
v += 1 # 找到没有父节点的点
dps(v)
print(max(dp[v][1], dp[v][0]))
结果验证: