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
Inputcopy | Outputcopy |
---|---|
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;
}