[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
", f[1][m]);
return 0;
}