【洛谷P2015】二叉苹果树【树形DP】

Description

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

2   5 
\   / 
 3 4 
 \ / 
  1 

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

Input

第1行2个数,N和Q(1<=Q<= N,1

Output

一个数,最多能留住的苹果的数量。

Sample Input

5 2
1 3 1
1 4 10
2 3 20
3 5 20

Sample Output

21

分析

解题思路
这是一道树形DP的板子题。
f[i][j]表示以i为根节点的子树,保留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]);
为什么是 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]
因为保留一条边必须保留从根节点到这条边路径上的所有边,那么如果你想从u的子节点v的子树上留边的话,也要留下u,v之间的连边。

另外,我的连接方式有点特别,用的是邻接表连接点,用邻接矩阵存边权。其实只用邻接矩阵就可以了,比较方便。

上代码

#include
#include
#include
#include
using namespace std;
int n,q,x,y,v,tot,h[101],e[101],f[101][101],s[101][101];
struct node
{
	int to,next;
}a[220];
void add(int x,int y)
{
	a[++tot]=(node){y,h[x]};
	h[x]=tot;
}
void dp(int k)
{
    e[k]=1;//记录当前节点已经被访问 
    for(int i=h[k];i>0;i=a[i].next)//,枚举边 
    {
    	if(e[a[i].to]==1) continue;//如果它的子节点已经被访问过,那就不用做了。 
    	dp(a[i].to);//递归 
    	for(int j=q;j>0;j--)//两个for类似01背包(选or不选且物品只有一个) 
    	{
    		for(int g=j-1;g>=0;g--)
    		{
    			f[k][j]=max(f[k][j],f[a[i].to][g]+f[k][j-g-1]+s[k][a[i].to]);
				// 这句详见博客 
    		}
    	}
    }
}
int main()
{
    cin>>n>>q;
    for(int i=1;i<=n-1;i++)
    {
    	cin>>x>>y>>v;
    	add(x,y);
    	add(y,x);
    	s[x][y]=s[y][x]=v;//记录权值 
    }
    dp(1);
    cout<<f[1][q];//q是要保留的树枝数量 
	return 0;
}

你可能感兴趣的:(DP,DFS)