P2015 二叉苹果树【树形DP】

题目描述
有一棵苹果树,如果树枝有分叉,一定是分 2 2 2叉(就是说没有只有 1 1 1个儿子的结点)
这棵树共有 N N N个结点(叶子点或者树枝分叉点),编号为 1 − N 1-N 1N,树根编号一定是 1 1 1
我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 4 4个树枝的树。

现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。
给定需要保留的树枝数量,求出最多能留住多少苹果。


输入格式
1 1 1 2 2 2个数, N N N Q ( 1 < = Q < = N , 1 < N < = 100 ) Q(1<=Q<= N,1Q(1<=Q<=N,1<N<=100)
N N N表示树的结点数, Q Q Q表示要保留的树枝数量。接下来 N − 1 N-1 N1行描述树枝的信息。
每行 3 3 3个整数,前两个是它连接的结点的编号。第 3 3 3个数是这根树枝上苹果的数量。
每根树枝上的苹果不超过 30000 30000 30000个。

输出格式
一个数,最多能留住的苹果的数量。


输入输出样例
输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20

输出
21


解题思路
这是一道树形 D P DP DP的板子题。
f [ i ] [ j ] f[i][j] f[i][j]表示以 i i i为根节点的子树,保留 j j j条树枝时的,保留的最大苹果数。
(这道题有一个隐含的条件,当某条边被保留下来时,从根节点到这条边的路径上的所有边也都必须保留下来)那么状态转移方程为:

  • f [ d e p ] [ j ] = m a x ( f [ d e p ] [ j ] , f [ s o n ] [ k ] + f [ d e p ] [ j − k − 1 ] + a [ s o n ] [ d e p ] ) ; f[dep][j]=max(f[dep][j],f[son][k]+f[dep][j-k-1]+a[son][dep]); f[dep][j]=max(f[dep][j],f[son][k]+f[dep][jk1]+a[son][dep]);

dep表示当前节点,son是dep的一个子节点,
为什么是 f [ d e p ] [ i − k − 1 ] f[dep][i-k-1] f[dep][ik1]而不是 f [ d e p ] [ i − k ] f[dep][i-k] f[dep][ik]
因为保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从uu的子节点vv的子树上留边的话,也要留下 u , v u,v u,v之间的连边。


代码

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int n,q,x,y,z,a[110][110],s[110][110],f[110][110],c[110],v[110];
void dfs(int dep){
	v[dep]=1;//每次循环时,给父结点一个标记,防止无限循环 
	for(int i=1;i<=c[dep];i++)
	{
		int son=s[dep][i];
		if(v[son]==1)
			continue;
		v[son]=1;
		dfs(son);
		for(int j=q;j>0;j--)//要倒序枚举因为这是01背包
		{
			for(int k=j-1;k>=0;k--)
			{
				f[dep][j]=max(f[dep][j],f[son][k]+f[dep][j-k-1]+a[son][dep]);
				// f[dep][j] 表示以 dep 为 子结点 可以选 j 条边
				// f[dep][j-K-1] 表示剩余给 son 结点的 兄弟结点(同一父节点的儿子) j-k-1 条边
				// a[son][dep] 表示 son 结点和 dep 结点之间的苹果数  
			}
		}
	}
} 
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n-1;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		a[x][y]=z;
		a[y][x]=z;//不知道哪个是父,哪个是子,要双向存
		s[x][++c[x]]=y;
		s[y][++c[y]]=x;
	}
	dfs(1);
	printf("%d",f[1][q]);
	return 0;
}

你可能感兴趣的:(树状DP)