洛谷 T692586:树上选点 ← 树形DP

【题目来源】
https://www.luogu.com.cn/problem/T692586

【题目描述】
给定一棵有 n 个结点的无向树,结点编号为 1∼n。
每个结点 i 有一个权值 ai(可以为负数、零或正数)。
现在要从这些结点中选出若干个,使得:
(1)对于树中的每一条边 (u,v),不能同时选中结点 u 和结点 v;
(2)所有被选中结点的权值之和尽可能大。
请你计算,在满足条件的前提下,可以得到的最大权值和是多少。

【输入格式】
第一行一个整数 n,表示结点数。
第二行包含 n 个整数 a1,a2,…,an,表示每个结点的权值。
接下来 n−1 行,每行两个整数 u,v,表示在结点 u 和结点 v 之间有一条无向边。
保证给定的边构成一棵树。

【输出格式】
输出一个整数,表示在满足“不选相邻点”条件下,选出的结点权值和的最大值。

【输入样例】
5
1 2 3 4 5
1 2
1 3
3 4
3 5​​​​​​​

【输出样例】
11

【数据范围】
对于全部数据:
1≤n≤20
−10^9≤ai≤10^9 
输入保证是一棵连通无向树。

【算法分析】
● 树形DP本质上就是在树结构上进行的动态规划。由于树具有天然的递归性质,因此我们可以将原问题分解为若干子树上的子问题。首先,先分别求解每一棵子树,然后再将这些子问题的解逐层向上合并,最终得到整棵树的最优解。

​​​​​​​● 如何把树变成DP能用的样子?
首先,选一个根节点。树原本是无向的,因此需要任选一个节点(比如 1 号节点)作为根,从而将无根树转化为有根树。这样一来,就能清晰地分辨出每个节点的父节点和子节点。
其次,用 DFS 递归遍历。由于父节点的状态依赖于子节点的状态,所以一般采用后序遍历的顺序(先递归处理完所有的子节点,最后再处理当前节点本身)。
最后,定义状态要紧扣问题。状态通常写作 dp[u][...],其中 u 表示当前节点,而方括号里的内容则需根据具体题目来设计——比如“选或不选当前节点”、“当前节点到叶子的最长距离”等等。

【算法代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=5e3+5;
vector<int> g[N];
LL val[N],dp[N][2];

void dfs(int u,int fa) {
    dp[u][1]=val[u];
    dp[u][0]=0;
    for(int v:g[u]) {
        if(v==fa)continue;
        dfs(v,u);
        dp[u][1]+=dp[v][0];
        dp[u][0]+=max(dp[v][0],dp[v][1]);
    }
}

int main() {
    int n;
    cin>>n;
    for(int i=1; i<=n; i++) cin>>val[i];
    for(int i=1; i<n; i++) {
        int u,v;
        cin>>u>>v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1,-1);
    cout<<max(dp[1][0],dp[1][1]);

    return 0;
}

/*
in:
5
1 2 3 4 5
1 2
1 3
3 4
3 5

out:
11
*/



【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/155581179
https://blog.csdn.net/hnjzsyjyj/article/details/140286717
https://blog.csdn.net/hnjzsyjyj/article/details/140309677

posted @ 2026-07-05 21:13  Triwa  阅读(13)  评论(0)    收藏  举报