对于求树形的最大值,不难联想到用dp来解决。那我们可以接着思考一下,它是否符合最优子结构的特点:对于一个有子树的根节点root,我们设子树的根节点为p,倘如我们知道子树在有/无p情况下(即上司来不来)各自的最优解,我们就可以得到整棵树在有无root结点的值,从而得到整棵树的最优解,即该题符合动态规划要求。
那好,接着是代码思路部分。对于树,我们常常会用到递归,倘若我们想求一棵树的最大快乐指数,就需要知道它的子树的最大快乐指数,从根节点出发,从上往下,从大子树到小子树,符合我们遍历树的习惯,再用数组记录一下重复的结点,这道题就能解决了。
首先,我们需要知道一棵树的最优解,那么就开f[6006]数组记录一下;又因为每个结点都有选或不选两种可能,所以需要开二维,用f[i][0]代表不选当前i结点,即i上司不来,[1]代表选。
再考虑一下上司来和不来两种情况:
1.上司来:即它的下属必然不来,即f[i][1]+=f[j]0
2.上司不来:需要注意的是,它的下属可能来,也可能不来,最优解取最大值,即f[i][0]+=max(f[j][0],f[j][1]);
且在每一层搜索中,为了方便思考,我们用 root:代表当前子树的根结点 exit:代表当前root结点是否选,即上司来不来。
到此,此题就结束了。
顺便补充一下,由于我是在看别人题解的时候才发现这道题只有一棵树(我本以为有可能有多棵树,害),所以我在main函数里遍历l每一个结点作为整棵树的根结点的情况,但影响不大,因为是记忆化搜索,所以重复点并不会真正进入递归,效率还是可以的。
题目传送口:
#include<cstdio>
#include<iostream>
#include <vector>
using namespace std;
int n;
int r[6005];//每个人的快乐指数
vector<int> vt[6005];//vt[i]:存储i结点的孩子结点
int f[60005][2];//f[i]代表以i为根结点的树,[0]代表不选i结点(上司不来),1代表选i结点,整个值代表最大的快乐指数
int ans=-99999;
inline int read(){//快读
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-'){
w=-1;
}
ch=getchar();
}
while(ch>='0'&&ch<='9'){
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
int dfs(int root,int exit)//root:当前子树的根结点 exit:代表当前root结点是否选,即上司来不来。
{
if(f[root][exit]){//记忆化
return f[root][exit];
}
int len=vt[root].size();//遍历root结点的子节点
if(len==0){//叶子结点 ,边界情况。
if(exit==0){
return f[root][0]=0;
}else{
return f[root][1]=r[root];
}
}
int son;//子节点的值
for(int i=0;i<len;++i){
son=vt[root][i];
if(exit==0){
f[root][0]+=max(dfs(son,0),dfs(son,1));//注意!!!root结点不去,它的子结点可以去或者不去!!!不是说子结点去,ans就一定大!!!
}else{
f[root][1]+=dfs(son,0);//root结点去,子节点就一定不去。
}
}
return exit==0?f[root][0]:(f[root][1]+=r[root]);//选root结点时记得加上它的快乐指数
}
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
r[i]=read();
}
int tmp1,tmp2;
for(int i=1;i<=n-1;++i)
{
tmp1=read();
tmp2=read();
vt[tmp2].push_back(tmp1);
}
for(int i=1;i<=n;++i)//遍历每一个结点作为根结点的情况。因为是记忆化搜索,所以不用担心有点被重复遍历。
{
ans=max(ans,max(dfs(i,0),dfs(i,1)));
}
printf("%d",ans);
return 0;
}