题解 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;
}