题目描述
有一棵苹果树,如果树枝有分叉,一定是分二叉(就是说没有只有一个儿子的结点)
这棵树共有 N 个结点(叶子点或者树枝分叉点),编号为 1∼N,树根编号一定是 1。
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 个树枝的树:
2 5
\ /
3 4
\ /
1
现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。
输入格式
第一行 2 个整数 N 和 Q,分别表示表示树的结点数,和要保留的树枝数量。
接下来 N−1 行,每行 3 个整数,描述一根树枝的信息:前 2 个数是它连接的结点的编号,第 3 个数是这根树枝上苹果的数量。
输出格式
一个数,最多能留住的苹果的数量。
输入输出样例
输入 #1复制
5 2
1 3 1
1 4 10
2 3 20
3 5 20
输出 #1复制
21
说明/提示
1⩽Q<N⩽100,每根树枝上的苹果 ⩽3×10
4
题目思路:
如何选择整个苹果树上二个枝条所挂的苹果数量最大化,当然题目还给了一个限制条件,必须从树根出发;显然我们只能采用深搜的方式,从树根一直搜索到树梢,然后从树梢返回过程中,来逐层计算最大值,并对最大值进行比较和保存,直到树根;一般来说,我们都会采用建双向边的方式,因为传统的思路是有去有回必定是双向边,其实这个思路会给题目增加空间复杂度,并且在这个思路之上我们还需要建一个vis数组来辅助dfs的搜索不会陷入死循环;换个思路,如果我们只建立一个有向图的话,那么我们就可以节省vis数组的使用,因为单项边是不会产生回头搜索的;这里面实质上还有一个是我们对dfs的了解多少问题,dfs本身就是一个一直往前直到终点,然后不断返回并在返回过程中计算我们所要的值,也就是dfs本身就包含了有递有归的天然属性;无需在考虑双向边实现树的递归;
代码详解: (仅供参考)
# include <bits/stdc++.h>
using namespace std;
const int N = 105;
struct edge
{
int u,v,w,next;
}e[N*2]; //此处如果采用双向边,那么就是e[N*2];单项边就取用e[N];此处的空间可以直接省一半;
int n,q,head[N],cnt = 1,f[N][N];
bool vis[N];
void add_edge(int u,int v,int w)
{
e[cnt] = {u,v,w,head[u]}; //采用集合的写法,减少行数,简洁代码
head[u] = cnt++;
}
void dfs(int u)
{
//vis[u] = 1; 单项边无需使用vis数组;
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
int w = e[i].w;
//if (vis[v]) continue; 这行其实不需要,因为本题是树,没有环,且我们使用了单项边构图
dfs(v);
for (int j = q; j >= 1; j--)
for (int h = 1; h <= j; h++)
f[u][j] = max(f[u][j],w + f[u][h-1] + f[v][j-h]);
// f[u][j] 等于一边取h-1条枝,一边取j-h条枝,再加上当前这条
}
}
int main()
{
scanf("%d%d",&n,&q);
for (int i = 1,u,v,w; i < n; i++)
{
scanf("%d%d%d",&u,&v,&w);
add_edge(u,v,w);
//add_edge(v,u,w); 本行其实不需要,也能节省一半的构图空间;
//有些人因为害怕题目数据会出反边,所以建立双向边,然而数据中没有反边
//反边也不符合本题所述的二叉树,并且我们采用的是dfs方法解题;
}
dfs(1);
printf("%d",f[1][q]);
return 0;
}
以上为个人见解,仅供参考;
题目扩展:如果题目要求不再限制必须从根出发来判断最大苹果数量的话,我们需要如何做?也就是对于n个点的苹果树,求相连的q个枝条,最大的苹果数量?欢迎大家指点。