[题目概述]
在X森林里,上帝创建了生命之树。
他给每棵树的每个节点(叶子也称为一个节点)上,都标了一个整数,代表这个点的和谐值。
上帝要在这棵树内选出一个非空节点集 S,使得对于 S 中的任意两个点 a,b,都存在一个点列
a
,
v
1
,
v
2
,
…
,
v
k
,
b
{a,v_1,v_2,…,v_k,b}
a,v1,v2,…,vk,b 使得这个点列中的每个点都是 S 里面的元素,且序列中相邻两个点间有一条边相连。
在这个前提下,上帝要使得 S 中的点所对应的整数的和尽量大。
这个最大的和就是上帝给生命之树的评分。
经过 atm 的努力,他已经知道了上帝给每棵树上每个节点上的整数。
但是由于 atm 不擅长计算,他不知道怎样有效的求评分。
他需要你为他写一个程序来计算一棵树的分数。
输入格式
第一行一个整数 n 表示这棵树有 n 个节点。
第二行 n 个整数,依次表示每个节点的评分。
接下来 n−1 行,每行 2 个整数 u,v,表示存在一条 u 到 v 的边。
由于这是一棵树,所以是不存在环的。
树的节点编号从 1 到 n。
输出格式
输出一行一个数,表示上帝给这棵树的分数。
数据范围
1
≤
n
≤
1
0
5
1≤n≤10^5
1≤n≤105
每个节点的评分的绝对值均不超过
1
0
6
10^6
106
输入样例:
5
1 -2 -3 4 5
4 2
3 1
1 2
2 5
输出样例:
8
题目就是我们从一颗树种,找到一个连通块,使得其中的结点权值之和最大
典型的树形DP问题,可以用dfs来做,每次向下遍历一个结点
所有连通块中,该连通块的权值之和是最大的
也给了我们一个思路,可以指定一个根节点,取他的孩子结点中权值之和大于0的结点的权值和添加到总权值上,这就是以此节点为根节点的连通块的权值最大值,最后再将所有结点取个max即可得到答案。
状态表示:f[u] 以u为根节点,包含u的连通块权值的最大值
状态计算:a,b,c为u的孩子结点,f[u] = w[u] + max(f[a], 0) + max(f[b], 0) + max(f[c], 0)
此处用邻接表存储树(不懂的可以看这个 算法题目中图和树的存储)
- 完整代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 100005, M = 2 * N;
// 最后的结果可能会爆int,需要用longlong存
typedef long long LL;
// h为邻接表,e[i]为i位置上的结点序号,ne[i]为i的next,idx为当前需要插入的结点
int h[N], e[M], ne[M], w[N], idx;
LL f[N];
int n;
// 为图添加边(也是树)
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
void dfs(int u, int father){
f[u] = w[u];
for(int i = h[u]; i != -1; i = ne[i]){
// j为一个孩子结点
int j = e[i];
// 防止往回遍历的情况
if(j != father){
dfs(j, u);
// 0ll因为max函数需要二者类型相同,都得为long long
f[u] += max(0ll, f[j]);
}
}
}
int main(){
cin >> n;
for(int i = 1; i <= n; i ++) cin >> w[i];
// 临界表都指向空
memset(h, -1, sizeof h);
for(int i = 0; i < n - 1; i ++){
int a, b;
cin >> a >> b;
// 没有方向,a->b, b->a 都得添加一下
add(a, b), add(b, a);
}
// 从第一个结点开始,父节点为-1
dfs(1, -1);
LL res = f[1];
for(int i = 2; i <= n; i ++){
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
- 本题的分享就结束了,主要是图和树的存储还要题目的分析
- 记得点赞关注加收藏!