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

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

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

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

#include <bits/stdc++.h>
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;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/80888486