C++ 树形DP经典例题详解——二叉苹果树

引言

这是十分经典的树形DP题,其转移方程很好想到,但有一些坑要注意


题目描述

有一棵苹果树,如果树枝有分叉,一定是分 2 叉(就是说没有只有 1 个儿子的结点)。这棵树共有 N 个结点(叶子点或者树枝分叉点),编号为1-N,树根编号一定是 1。 我们用一根树枝两端连接的结点的编号来描述一根树枝的位置。下面是一颗有 4 个树枝的树:  现在这颗树枝条太多了,需要剪枝。但是一些树枝上长有苹果。 给定需要保留的树枝数量,求出最多能留住多少苹果。

输入

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

输出

第1行:一个整数,表示最多能留住的苹果的数量。

样例输入

Copy (如果复制到控制台无换行,可以先粘贴到文本编辑器,再复制)

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

样例输出

21

思路

对于这一题,可以很快想到动态转移方程

dp[ i ][ j ] = max ( dp[ i ][ j ] , dp[ i ][ j-k ] + dp[ s ][ k-1 ] + G[ x ][ s ] )

s 表示是 i 的儿子,G[ x ][ i ] 为该树枝上的苹果数、

我们可以想象一下,在 i 节点的子树中,减去其儿子的 k 条边,但不减去与儿子 s 节点的边,就是只减去了 k - 1 条边

然后在实现的过程中,子树的边数是不好单独求的,所以就在DFS中逐一求子树的边数

就用 sum += dfs( s , x ) 来求得边数,在最后还要加一,应为还要算上自己的这条边


代码

结合代码理解啦

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <vector>
using namespace std;
 
int n , m , dis[107][107] , dp[107][107] ;
vector <int> G[107] ;
 
int dfs( int x , int fa )
{
    int sum = 0 ;
    for(int i = 0 ; i < G[x].size() ; ++ i )
    {
        int s = G[x][i];
        if( s == fa )
            continue;
        sum += dfs( s , x );//求边数和
        sum = sum + 1 ;
        for(int j = m ; j >= 1 ; -- j ) {
            for(int k = 1 ; k <= j ; ++ k ) {//边数算上自己必须减去一条
                dp[x][j] = max( dp[x][j] , dp[x][j-k] + dp[s][k-1] + dis[x][s] );
                //转移求最优解
            }
        }
    }
    return sum;
}
 
int main()
{
    scanf("%d%d", &n , &m );
    for(int i = 1 ; i < n ; ++ i )
    {
        int a , b ,c ;
        scanf("%d%d%d", &a , &b , &c );
        dis[a][b] = dis[b][a] = c ;//表示 a 到 b 的边权
        G[a].push_back(b);
        G[b].push_back(a);   
    }
    dfs( 1 , 0 );
    printf("%d", dp[1][m] );
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_44013342/article/details/88555708