CTSC1997选课——之简单树形背包剖析

树形背包入门————

写了一道树形背包题目,想了挺久的,把我的思路分享一下。

这里引用一道基础树形背包题目来配合讲解。

洛谷:P2014 CTSC1997选课

题意:在森林中每个点都有权值,选择M个点,使得总权值最大。(选择一个点,当且仅当它是根节点或者它的父节点被选择)

感觉对于我这种不太聪明的初学者很不友好,dp讲究一个寻找子问题,区间dp和线性dp一般直接根据题意找子问题在推出更大的问题。这道题虽然也要寻找子问题,但是不太容易,我们先把自己代入进去,看看能不能找到什么大问题和小问题的关联。
CTSC1997选课——之简单树形背包剖析_第1张图片

    比如这颗树,你想象你是根节点,然后你要选3个点,3个点可以当成3元钱,选一个点花一块钱,那你需要考虑什么呢?

首先是选哪些点比较划算呢?对于根,有两种选择,老大和老二,那么第一状态就出来了,就是选择的结点。

假如我选老大,那么我还要考虑我要在老大这边花多少钱。
第二状态就出来了,分配的钱数。

状态找出来之后,目前就可以用dp[i][j]表示给i号结点j元钱之后,他能带给你的最大回报。

由于选点的连续性,你要选了之后才知道答案,这时候老大就说了,放心,把钱交给我,我告诉你答案,那么你把钱交给他了,他也面临同样的问题,于是把钱交给4号,4号交给7号,7号是叶子节点,同时也是最终子问题,7号就说了只要给我钱,给我多少都只能在我这里拿到相同的权值a[7]。4号得到了反馈,就说,给我一块钱可以拿到a[4],给我两块钱以上可以拿到a[4]+a[7]…如此层层反馈,问题便得到解决了。。

根据上述描述,可以发现是一种递归的性质,没错,树形背包需要靠dfs的递归来完成,还有一点,就是这个图可能是一个森林,我们用一个0节点把其他根节点连起来,变成一棵树,方便处理,最后输出dp[0][m+1]

其实背包问题考虑的就是我有多少钱?买哪个东西?花多少钱?

下面给出代码与解释。

#include 
#include 
#include 
#include 
const int MAXN=2050;
using namespace std;

int n,m,cnt,a[MAXN],h[MAXN],dep[MAXN],s[MAXN],maxd[MAXN],val[MAXN],dp[500][500];

struct tu						///这里邻接表
{
    int next,to,from,val;
}e[MAXN];

void add(int x,int y)		    ///邻接表连边
{
    e[++cnt].to=y;
    e[cnt].next=h[x];
    h[x]=cnt;
}

void dfs(int now,int fath)		///核心
{
    for(int i=h[now];i;i=e[i].next){	///老大还是老二?
        int v=e[i].to;
        if(v!=fath){					///这个点不能是父节点
            dfs2(v,now);			
            for(int j=m+1;j>=1;--j)		///当前点初始有多少钱?
                for(int k=0;k<j;++k)	///分配给下个点多少钱?
                    dp[now][j]=max(dp[now][j-k]+dp[v][k],dp[now][j]);
        }
    }
}

int main()
{
    int i,j,k,l,r;
    cin >> n >> m ;
    for(i=1;i<=n;++i){
        scanf("%d%d",&j,&k);
        a[i]=k;
        if(j){add(j,i);s[i]=1;}
        for(int j=m+1;j>=1;--j)
            dp[i][j]=max(dp[i][j],a[i]);
    }
    for(i=1;i<=n;++i)
        if(!s[i])add(0,i);
    dfs2(0,0);
    cout << dp[0][m+1] << endl ;
    return 0;
}

<----To be continue

你可能感兴趣的:(树形dp)