P2015 二叉苹果树 树形dp
解题思路
问题分析
这是一道树形动态规划问题,需要在二叉树上选择保留一定数量的树枝(边),使得保留的树枝上的苹果总数最大。关键点在于:
-
树枝形成一棵以根节点为根的子树
-
需要保留恰好 q 条树枝
-
树枝上的苹果数需要最大化
动态规划状态设计
定义 dp[u][j]:表示以节点 u 为根的子树中,保留 j 条边时能获得的最大苹果数
状态转移方程
对于每个节点 u 和它的子节点 v(连接边权值为 w):
-
如果选择保留 u-v 这条边,则可以分配 k 条边给 v 的子树(0 ≤ k ≤ j-1)
-
转移方程:
dp[u][j] = max(dp[u][j], dp[u][j-1-k] + dp[v][k] + w)解释:
-
j-1-k:给其他子树保留的边数(总边数 j 减去当前边 1 再减去给子树的 k) -
dp[v][k]:子树 v 保留 k 条边的最优解 -
w:当前边 u-v 上的苹果数
-
关键点说明
-
为什么是 j-1:因为要保留 u-v 这条边(占1条边),所以子树 v 最多只能分配 j-1 条边
-
倒序枚举 j:类似于背包问题的空间优化,避免重复计算
-
二叉树特性:虽然代码可以处理多叉树,但题目保证是二叉树
代码注释
#include<bits/stdc++.h> #define pii pair<int,int> // 定义pair类型别名,存储<节点编号, 边权值> using namespace std; const int N = 1e3 + 10; // 数组大小 vector<pii> g[N]; // 邻接表存储树,g[u] = vector<pair<v, w>> int n, q; // n-节点数,q-要保留的边数 int dp[N][N]; // dp[u][j]: 以u为根的子树保留j条边的最大苹果数 void dfs(int u, int fa) { // 遍历u的所有子节点 for(int i = 0; i < g[u].size(); i++) { int v = g[u][i].first, w = g[u][i].second; // v-子节点,w-边上的苹果数 if(v == fa) continue; // 避免回溯父节点 dfs(v, u); // 递归处理子节点 // 动态规划转移(类似背包问题倒序枚举) for(int j = q; j >= 1; j--) { // 枚举当前可用的总边数 for(int k = 0; k <= j - 1; k++) { // 分配给子节点v的边数(最多j-1条) // 状态转移:选择u-v边(w苹果),并分配k条边给v的子树 dp[u][j] = max(dp[u][j], dp[u][j - 1 - k] + dp[v][k] + w); } } } } int main() { cin >> n >> q; // 输入节点数和要保留的边数 // 建树(注意题目给出的是无向边) for(int i = 1; i < n; i++) { int x, y, z; cin >> x >> y >> z; // 输入边的两个端点和苹果数 g[x].push_back({y, z}); g[y].push_back({x, z}); // 无向图,双向添加 } dfs(1, 0); // 从根节点1开始DFS,初始父节点设为0 cout << dp[1][q]; // 输出根节点保留q条边的最大苹果数 return 0; }

浙公网安备 33010602011771号