P2015 二叉苹果树 树形dp

解题思路

问题分析

这是一道树形动态规划问题,需要在二叉树上选择保留一定数量的树枝(边),使得保留的树枝上的苹果总数最大。关键点在于:

  1. 树枝形成一棵以根节点为根的子树

  2. 需要保留恰好 q 条树枝

  3. 树枝上的苹果数需要最大化

动态规划状态设计

定义 dp[u][j]:表示以节点 u 为根的子树中,保留 j 条边时能获得的最大苹果数

状态转移方程

对于每个节点 u 和它的子节点 v(连接边权值为 w):

  1. 如果选择保留 u-v 这条边,则可以分配 k 条边给 v 的子树(0 ≤ k ≤ j-1)

  2. 转移方程:
    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 上的苹果数

关键点说明

  1. 为什么是 j-1:因为要保留 u-v 这条边(占1条边),所以子树 v 最多只能分配 j-1 条边

  2. 倒序枚举 j:类似于背包问题的空间优化,避免重复计算

  3. 二叉树特性:虽然代码可以处理多叉树,但题目保证是二叉树

代码注释

#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;
}

 

posted @ 2025-06-12 16:03  CRt0729  阅读(43)  评论(0)    收藏  举报