题解 P2015 【二叉苹果树】

题目链接
\(\mathcal{}\)
**
这题是我在某奥赛一本通(提高篇)——树形DP中的第一道例题,抱着试一试的心态,本蒟蒻尝试了这道题。在过了n小时后,终于做出了这题
以上纯属扯淡
**

切入正题:

首先,我们仔细看一下题目,可以发现——这是一棵树呀!所以,我们就需要用到树上DP。
那么,我们应该怎么设置状态呢?

观看上面这张丑陋的图,红色的数字表示这条树枝所有的苹果数量。
我们有没有了一些灵感,如果没有,那么我在来给一些提示

这道题的决策

1.保留这根树枝,获得它的苹果数

2.不保留这根树枝

So,我们的状态有一维是树枝的数量
** 那还有什么和状态相关的变量呢?没错,以第i个节点为根的子树
(因为我们可以将这一棵大的子树分成若干个子树的值来求出),所以,我们的状态就出来了,设\(f[i][j]\)表示以 \(i\) 为根节点保存 \(j\) 根树枝的可以获得的最大苹果数**
那么,\(f[i][j]=max(f[i][j],f[left][j]+e[left].apple+f[right][k-j]+e[right].apple)\)
但是,这个转移方程是有问题的,\(Why???\)
我们来仔细看一下方程,\(left\)表示\(i\)节点的左儿子,\(right\)表示\(i\)的右儿子,\(e[i].apple\)表示第i条树枝的苹果数,看上去没毛病。但是,我们结合以下上面丑陋的图,再来看一下
\(i=3\)时,\(f[i][1]=20\)\(f[i][2]=40\),但是,当 \(i=1\)时,我们如果给 \(3\)号节点分配两根树枝,那么它会给你一个值,40,也就是说 \(f[1][2]=40\),不对啊!如果我们给了3号节点2根树枝,那么1号节点就没有与3号节点相连的树枝,那么根本就不可能有 \(40\)个苹果呀(如果不能理解,可以这么想象,虽然然每条树枝上都有可以获得到的 \(apple\),但是如果你没有放到 \(1\)号节点,这些苹果就都不是你的)!
回到转移方程里面,我们可以发现,我们直接分配给了 \(left\)节点和 \(right\)节点 \(j\)根树枝和 \(i-j\)根树枝, \(but\),我们并没有考虑 \(left\)节点和 \(i\)节点之间相连的树枝, \(right\)节点也是一样的,\(So,\)我们得将转移方程改一下
\(f[i][j]=max(f[i][j],f[left][j-1]+e[left].apple+f[right][k-j-1]+e[right].apple\)
好了现在的就应该对了,我们把细节留在程序里面
\(\mathcal{Code:}\)

#include <iostream>
#include <cstdio>
using namespace std;
struct node
{
    int t; //这条边通向的节点
    int apple; //第i条树枝的苹果数
    int next; //第i条边的下一条边
};
node e[2*101];
int dp[101][101];
int head[101],n,q,tot=0;
void add(int x,int y,int z)  //邻接表存数
{
    e[++tot].t=y;
    e[tot].apple=z;
    e[tot].next=head[x];
    head[x]=tot;
}
void dfs(int f,int fa,int apple) //递归遍历这棵树
{
    int son[101]={0},cnt=0; //son[1]表示f的左儿子在第几条边,son[2]表示f的右儿子在第几条边
    bool flag=false;
    for(int xun=head[f];xun;xun=e[xun].next)
    {
        if(e[xun].t!=fa)
        {
        	flag=true;
            son[++cnt]=xun;
            dfs(e[xun].t,f,e[xun].apple);
        }
    }
    if(!flag) //如果没有儿子,说明它是叶子结点,直接回溯
    {
        return;
    }
    for(int i=1;i<=q;i++) //DP部分
    {
        for(int j=0;j<=i;j++)
        {
        	int t1=0;
        	if(j-1>=0) t1+=e[son[1]].apple;  //j-1>=0表示分配给了左儿子与i节点的一条相连的树枝
        	if(i-j-1>=0) t1+=e[son[2]].apple;//i-j-1>=0表示分配给了右儿子与i节点的一条相连的树枝
        	if(j!=0)
         		dp[f][i]=max(dp[f][i],dp[e[son[1]].t][j-1]+t1+dp[e[son[2]].t][i-j-1]);  //j!=0,表示两个儿子都分配了
         	else //j==0,表示只分配给了右儿子树枝
         		dp[f][i]=max(dp[f][i],dp[e[son[2]].t][i-j-1]+t1);
        }
    }
}
int main()
{
    scanf("%d %d",&n,&q);
    for(int i=1;i<=n-1;i++)
    {
        int x,y,z;
        scanf("%d %d %d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);
    }
    dfs(1,0,0);
    printf("%d",dp[1][q]); //因为最终我们要求的苹果数是以1为根节点的子树中保留q根树枝的最大苹果数,所以最终的结果等于dp[1][q]
    return 0;
}

谢谢观看,如果本题解有什么不对的地方或有什么不懂的地方,请私信本人(反正我也是这么过来的

posted @ 2019-08-02 11:06  zhz小蒟蒻  阅读(192)  评论(0编辑  收藏  举报