题意分析
给定一棵无根树,你可以从中去掉一些子树(但不能使原树为空),求最后剩下的树的最大权值和。
最大子段和问题
我们先回顾一下最大子段和。
其解法是令 $dp_i$ 表示以 $a_i$ 结尾的最大子段和,那么就有:
\[dp_i=\max(dp_{i-1}+a_i,a_i)\]即接到前面一项和不接到前面一项之后两种情况。
最大子树和
树——半线性结构
相比于数组等纯线性,图的非线性,树是半线性的。
从叶子节点到根节点就是一条确定且唯一的路径。
因此我们可以考虑树形 DP来解决此问题。
树形 DP
令 $dp_x$ 表示节点 $x$ 的子树内的最大子树和。
那么对于每一个节点 $i$,显然有初始值 $dp_i=a_i$。
从叶子节点到根节点递推时,对于节点 $x$ 的子节点 $y_1,y_2,y_3,\cdots,y_k$ 就有:
\[dp_x\leftarrow\max(dp_x,dp_x+dp_{y_i})\]递推式得到了,那么就是求解了。
一般情况下,树形 DP 都会写成递归的形式——在此处就是先求出子节点的答案,才能求出父节点的答案。
注意原树不能为空,因此最后取最大值时,不能赋初始值为 $0$,最终答案有可能小于 $0$。
AC 代码
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
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=16000;
struct graph{
struct edge{
int v,r;
}a[2*(N-1)+1];
int h[N+1];
void create(int u,int v){
static int top;
a[++top]={v,h[u]};
h[u]=top;
}
}g;
int n,a[N+1],dp[N+1];
void dfs(int x,int fx){
dp[x]=a[x];
for(int i=g.h[x];i>0;i=g.a[i].r){
if(g.a[i].v==fx)continue;
dfs(g.a[i].v,x);
//这里其实是等价的,这样效率略高
if(dp[g.a[i].v]>0)dp[x]+=dp[g.a[i].v];
}
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",a+i);
}
for(int i=1;i<n;i++){
int u,v;
scanf("%d %d",&u,&v);
g.create(u,v);
g.create(v,u);
}
dfs(1,0);
int Max=-2147483647;
for(int i=1;i<=n;i++){
Max=max(Max,dp[i]);
}printf("%d\n",Max);
/*fclose(stdin);
fclose(stdout);*/
return 0;
}