树形背包学习笔记 + 洛谷2014 CSTS1997 选课(树形背包模板题)

题目链接
题意:
给你一个森林,有 n n 个点,每个点有一个价值,能选某个点的前提是它的所有祖先节点都被选,让你求只能选 m m 个节点的最大价值。
题解:
首先先把所有没有父节点的点向 0 0 号点连边,让森林变成一棵树,然后我们由叶子向根dp,设 dp[i][j] d p [ i ] [ j ] 为在根节点为 i i 的子树里选 j j 个节点的最大获益。当 j=0 j = 0 时, dp[i][j]=0 d p [ i ] [ j ] = 0 。当 j0 j ≠ 0 时,设 yson[x] y ∈ s o n [ x ] c[1,n] c ∈ [ 1 , n ]

dp[i][j]=max(i=1son[x]dp[y][c])+val[x] d p [ i ] [ j ] = m a x ( ∑ i = 1 s o n [ x ] d p [ y ] [ c ] ) + v a l [ x ]

这可以转化为一个分组背包,每个子树看作一组,从每组中至多选一个物品,体积为 j j ,价值为 dp[y][j] d p [ y ] [ j ] ,这样对这个树形结构进行背包dp的转移就行。
复杂度 O(n3) O ( n 3 ) ,注意 0 0 号点是个虚拟节点,实际上不需要被选, dp[0][m] d p [ 0 ] [ m ] 就是最终答案。
洛谷2014选课代码:

#include 
using namespace std;

int n,m,son[310][310],dp[310][310],val[310],cnt[310];
void dfs(int x)
{
    for(int i=1;i<=cnt[x];++i)
    {
        int y=son[x][i];
        dfs(y);
        for(int j=m;j>=0;--j)
        {
            for(int k=j;k>=0;--k)
            dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[y][k]);
        }
    }
    if(x!=0)
    {
        for(int i=m;i>=1;--i)//注意倒叙枚举 
        dp[x][i]=dp[x][i-1]+val[x];//x本身需要占一门课
    }   
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        int x;
        scanf("%d%d",&x,&val[i]);
        son[x][++cnt[x]]=i;
    }
    dfs(0);
    printf("%d\n",dp[0][m]);
    return 0;
}

你可能感兴趣的:(树形dp,dp,学习笔记)