二叉树树形背包:枚举左右子树各保留多少条边完成状态转移。
OJ: roj
题目 ID: 1575
难度:普及
标签:动态规划树形 DP背包 DP
创建: 2026-09-30 19:26
更新: 2026-10-06 00:48
题目描述
有一棵
输入第一行是
数据范围:
思路
设
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 19:30
# update_at: 2026-09-30 19:30
import sys
from functools import cache
def solve() -> None:
data = list(map(int, sys.stdin.read().split()))
if not data:
return
it = iter(data)
n, q = next(it), next(it)
adj: list[list[tuple[int, int]]] = [[] for _ in range(n + 1)]
for _ in range(n - 1):
u, v, w = next(it), next(it), next(it)
adj[u].append((v, w))
adj[v].append((u, w))
# 构建二叉树有向结构:children[u] = [(v1, w1), (v2, w2)]
children: list[list[tuple[int, int]]] = [[] for _ in range(n + 1)]
visited = [False] * (n + 1)
visited[1] = True
queue = [1]
for u in queue:
for v, w in adj[u]:
if not visited[v]:
visited[v] = True
children[u].append((v, w))
queue.append(v)
# 记忆化搜索:dfs(u, k) 表示在以 u 为根的子树中保留 k 条边能获得的最大苹果数
@cache
def dfs(u: int, k: int) -> int:
"""在以 u 为根的子树内保留 k 条边的最大权值和。"""
if k == 0 or not children[u]:
return 0
# 根据题意分叉一定是两叉或 0 叉(叶子)
(l, wl), (r, wr) = children[u]
# 分三种情况:只选左、只选右、两边分配
return max(
dfs(l, k - 1) + wl,
dfs(r, k - 1) + wr,
max(
(dfs(l, i) + wl + dfs(r, k - 2 - i) + wr for i in range(k - 1)),
default=0,
),
)
print(dfs(1, q))
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 00:48
* update_at: 2026-10-06 00:48
*/
#include <cstdio>
#include <vector>
#include <utility>
using namespace std;
typedef long long ll;
const int MAXN = 105;
const int MAXQ = 105;
int n, q;
// children[u][0] = (左子节点, 边权); children[u][1] = (右子节点, 边权)
// 叶子节点对应的 children 为空
vector<pair<int, int> > children[MAXN];
// dp[u][k] 表示以 u 为根的子树中保留 k 条边能获得的最大苹果数
int dp[MAXN][MAXQ];
// 构建以根节点 1 为起点的有向二叉树
void build_tree() {
// 用邻接表读取无向边
vector<pair<int, int> > adj[MAXN];
for (int i = 1; i <= n - 1; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
adj[u].push_back(make_pair(v, w));
adj[v].push_back(make_pair(u, w));
}
// 从根节点 1 开始 BFS,把子节点加入 children
int queue[MAXN];
int head = 0, tail = 0;
bool visited[MAXN] = {false};
visited[1] = true;
queue[tail++] = 1;
while (head < tail) {
int u = queue[head++];
for (int i = 0; i < (int)adj[u].size(); i++) {
int v = adj[u][i].first;
int w = adj[u][i].second;
if (!visited[v]) {
visited[v] = true;
children[u].push_back(make_pair(v, w));
queue[tail++] = v;
}
}
}
}
// 树形背包 DFS:计算以 u 为根的子树保留 k 条边的最大权值
int dfs(int u, int k) {
if (k == 0 || children[u].empty()) {
return 0;
}
if (dp[u][k] != -1) {
return dp[u][k];
}
int l = children[u][0].first;
int wl = children[u][0].second;
int r = children[u][1].first;
int wr = children[u][1].second;
int res = 0;
// 只保留左子树:必须选边 (u, l)
res = max(res, dfs(l, k - 1) + wl);
// 只保留右子树:必须选边 (u, r)
res = max(res, dfs(r, k - 1) + wr);
// 左右子树都保留:两条边必选,剩余 k-2 条边在左右子树之间分配
for (int i = 0; i <= k - 2; i++) {
res = max(res, dfs(l, i) + wl + dfs(r, k - 2 - i) + wr);
}
dp[u][k] = res;
return res;
}
int main() {
scanf("%d %d", &n, &q);
build_tree();
// 初始化记忆化数组
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= q; j++) {
dp[i][j] = -1;
}
}
printf("%d\n", dfs(1, q));
return 0;
}