二叉苹果树 洛谷 - P2015

Description

有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)

这棵树共有 NN 个结点(叶子点或者树枝分叉点),编号为 1∼N1∼N,树根编号一定是 11。

我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 44 个树枝的树:

2   5
 \ / 
  3   4
   \ /
    1

Copy

C++

现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。

给定需要保留的树枝数量,求出最多能留住多少苹果。

Input

第一行 22 个整数 NN 和 QQ,分别表示表示树的结点数,和要保留的树枝数量。

接下来 N−1N−1 行,每行 33 个整数,描述一根树枝的信息:前 22 个数是它连接的结点的编号,第 33 个数是这根树枝上苹果的数量。

Output

一个数,最多能留住的苹果的数量。

Sample 1

InputcopyOutputcopy
5 2
1 3 1
1 4 10
2 3 20
3 5 20
21

Hint

1⩽Q<N⩽1001⩽Q<N⩽100,每根树枝上的苹果 ⩽3×104⩽3×104。

解决方案

#include <iostream>
#include <cstring>  // 使用memset替代循环初始化 
#include <algorithm>
using namespace std;
 
const int MAX_NODES = 205; // 最大节点数 
 
// 边结构体定义(重命名变量增强可读性)
struct Edge {
    int to;      // 目标节点 
    int next;    // 下一条边索引 
    int from;    // 起始节点(原dq)
    int weight;  // 边权值(val原)
};
 
int n, q;        // 节点总数和需要保留的边数 
Edge edges[MAX_NODES*2]; // 邻接表存储(树的双向边)
int head[MAX_NODES    ]; //头 指针数组 
int edge_cnt = 1;        // 边计数器(从1开始)
 
// 动态规划数组:f[node][k] 表示以node为根的子树选择k条边的最大权值 
int f[MAX_NODES][MAX_NODES];
 
// 添加双向边(优化参数命名)
void addEdge(int u, int v, int w) {
    // 正向边 
    edges[edge_cnt] = {v, head[u], u, w};
    head[u] = edge_cnt++;
    // 反向边 
    edges[edge_cnt] = {u, head[v], v, w};
    head[v] = edge_cnt++;
}
 
/**
 * 树形DP深度优先搜索(优化函数命名)
 * @param current 当前节点 
 * @param parent  父节点(避免回环)
 */
void dfsWithDP(int current, int parent) {
    for(int i = head[current]; i != -1; i = edges[i].next) {
        int child = edges[i].to;
        if(child == parent) continue; // 跳过父节点 
        
        // 递归处理子树 
        dfsWithDP(child, current);
        
        // 初始化:选择1条边的情况即当前边 
        f[child][1] = edges[i].weight;
        
        /* 背包式状态转移(优化循环顺序防止重复计算)
         * 1. 倒序遍历保证无后效性 
         * 2. 合并根节点特殊情况判断条件 
         */
        for(int j = q; j >= 1; --j) {
            for(int k = 0; k <= j; ++k) {
                // 根节点允许任意组合,非根节点需要保持子结构 
                if(current == 1 || (k != 0 && j != k)) {
                    f[current][j] = max(f[current][j], f[child][k] + f[current][j - k]);
                }
            }
        }
    }
}
 
int main() {
    // 初始化头指针数组(使用memset优化)
    memset(head, -1, sizeof(head));
    
    cin >> n >> q;
    // 读取n-1条边(树结构特征)
    for(int i = 1; i < n; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        addEdge(u, v, w);
    }
    
    // 从根节点(假设为1)开始执行DP 
    dfsWithDP(1, 0);
    cout << f[1][q];
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值