生命之树 (搜索 || 树形DP)
搜索:
直接把学长的题解拉过来吧。
确定一个点为树根,同时作为初始点累加权值,来寻找包括这点的最大连通权值和。对每个子节点如果其对应子树不存在正连通,不予考虑;否则,累加子树中包含这个子节点的最大连通权值和。可以看出这是个分治策略,可以递归实现。
设 F(x)为 x 子树中包含点 x 的最大连通权值和,xi为 x 子节点
( 分析一下复杂度,对每个点进行一次搜索,每次搜索 O(n),总 复杂度 O(n^2),只能过 30%的测试点。如何优化:
重复搜索肯定有大量的相同子问题 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 ",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); } }
上一篇:
通过多线程提高代码的执行效率例子