搜索:
直接把学长的题解拉过来吧。
确定一个点为树根,同时作为初始点累加权值,来寻找包括这点的最大连通权值和。对每个子节点如果其对应子树不存在正连通,不予考虑;否则,累加子树中包含这个子节点的最大连通权值和。可以看出这是个分治策略,可以递归实现。
设 F(x)为 x 子树中包含点 x 的最大连通权值和,xi为 x 子节点
如何优化:
重复搜索肯定有大量的相同子问题 F(X),其实只需要搜一次即可。
任意确定一个树根,红色连通块为最优解。
可以看出有三类点:
a: 最优连通块中最高的点;
b: 最优连通块中其余的点;
c: 剩余点。
进行一次以树根开始的搜索过程即可求出所有 F(a),F(b),F(c)。 F(a)即为最优解,因此只需在一次搜索中取 max(F(x))即可。
C++代码:这题出了个很变态的数据,一颗从1到n相邻连接的一条龙的树。。两万多层,我用Java交递归爆栈了。。。
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN=1e5+5;
LL num[MAXN];
vector<int> tree[MAXN];
LL ans=-1e7;
LL dfs(int f,int x){
LL sum=num[x];
for(vector<int>::iterator it=tree[x].begin();it!=tree[x].end();it++)
if(*it!=f)
sum+=max(0LL,dfs(x,*it));
ans=max(ans,sum);
return sum;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",num+i);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
tree[a].push_back(b);
tree[b].push_back(a);
}
dfs(0,1);
printf("%lld\n",ans);
return 0;
}
树形DP:
思路上其实一样,只添加正连通块
代码:
import java.util.*;
public class Main{
static Scanner sc = new Scanner(System.in);
static int n;
static long res = -10000007;
static int [] point;
static int [] vis;
static Vector<Integer>[] tree;
static long [] dp;
static long sum;
static void dfs(int now) {
dp[now] = point[now];
vis[now] = 1;
long sum = 0;
int len = tree[now].size();
for(int i = 0; i < len; i++) {
int tmp = tree[now].get(i);
if(vis[tmp] != 1) {
dfs(tmp);
if(dp[tmp] > 0)
sum += dp[tmp];
}
}
if(sum > 0) dp[now] += sum;
res = Math.max(res, dp[now]);
}
public static void main(String[] args) {
n = sc.nextInt();
point = new int[n+1];
vis = new int[n+1];
dp = new long[n+1];
tree = new Vector[n+1];
for(int i = 0; i <= n; i++)
tree[i] = new Vector<Integer>();
for(int i = 1; i <= n; i++)
point[i] = sc.nextInt();
for(int i = 0; i < n-1; i++) {
int a = sc.nextInt(), b = sc.nextInt();
tree[a].add(b);
tree[b].add(a);
}
dfs(1);
System.out.println(res);
System.exit(0);
}
}