没有上司的舞会(记忆化dfs详解)

对于求树形的最大值,不难联想到用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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值