[Luogu] P2015 二叉苹果树
[Luogu] P2015 二叉苹果树
1.题目
题目描述
有一棵苹果树,如果树枝有分叉,一定是分2叉(就是说没有只有1个儿子的结点)
这棵树共有N个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有4个树枝的树2 5 \ / 3 4 \ / 1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。\输入输出格式
输入格式:
第1行2个数,N和Q(1<=Q<= N,1<N<=100)。
N表示树的结点数,Q表示要保留的树枝数量。接下来N-1行描述树枝的信息。
每行3个整数,前两个是它连接的结点的编号。第3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过30000个。输出格式:
一个数,最多能留住的苹果的数量。
输入输出样例
输入样例#1:
5 2 1 3 1 1 4 10 2 3 20 3 5 20
输出样例#1:
21
2.题解
树形DP。
dfs,然后对各节点边统计子树边DP。
设\(f[i][j]\)为当前为第\(i\)个点,在其子树上选择了\(j\)条边后的最大边权和,\(size[i]\)为以\(i\)为根节点的子树上的边数。对当前点跑一个背包。
转移方程为\(f[i][j] = \max(f[i][j], f[i][j - k - 1] + f[v][k] + w)\),其中\(1 \leqslant j \leqslant \min(size[i], m)\),\(v \in i's~subtree\),\(0 \leqslant k \leqslant \min(j - 1, size[v])\),\(w\)为当前连接\(i\)与\(v\)的边权。
\(j, v\)的取值都比较好理解,\(k \geqslant 0\)的原因是可以只选当前边,不考虑子树。\(\leqslant j - 1\)的原因是需要为当前边预留位置。
#include <cstdio>
const int MAXN = 110;
struct EDGE{
int to, next, wgh;
}edge[MAXN << 1];
int n, m, top, u, v, w;
int f[MAXN][MAXN], size[MAXN], head[MAXN];
template <class T>
inline T min(T a, T b) {return (a < b ? a : b);}
template <class T>
inline T max(T a, T b) {return (a > b ? a : b);}
inline void add_edge(int u, int v, int w) {
edge[++top].to = v;
edge[top].wgh = w;
edge[top].next = head[u];
head[u] = top;
edge[++top].to = u;
edge[top].wgh = w;
edge[top].next = head[v];
head[v] = top;
return ;
}
inline void dfs(int now, int fa) {
int tmp(0), tmw(0);
for (int i = head[now]; i; i = edge[i].next)
if((tmp = edge[i].to) != fa) {
dfs(tmp, now); size[now] += size[tmp] + 1;
tmw = edge[i].wgh;
for (int j = min(size[now], m); j >= 1; --j)
for (int k = min(j - 1, size[tmp]); ~k; --k)
f[now][j] = max(f[now][j], f[now][j - k - 1] + f[tmp][k] + tmw);
}
return ;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i < n; ++i)
scanf("%d%d%d", &u, &v, &w), add_edge(u, v, w);
dfs(1, 1);
printf("%d\n", f[1][m]);
return 0;
}