[NOIP2003 提高组] 加分二叉树

[NOIP2003 提高组] 加分二叉树

题目描述:

设一个 n 个节点的二叉树 tree 的中序遍历为(1,2,3,…,n),其中数字 1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i 个节点的分数为 di​,tree 及它的每个子树都有一个加分,任一棵子树 subtree(也包含 tree 本身)的加分计算方法如下:

subtree 的左子树的加分 乘 subtree 的右子树的加分 加 subtree 的根的分数。

若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 (1,2,3,…,n) 且加分最高的二叉树 tree。要求输出

  1. tree 的最高加分。

  2. tree 的前序遍历。

输入格式:

第 1 行 1 个整数 n,为节点个数。

第 2 行 n 个用空格隔开的整数,为每个节点的分数

输出格式:

第 1 行 1 个整数,为最高加分(Ans≤4,000,000,000)。

第 2 行 n 个用空格隔开的整数,为该树的前序遍历。

输入输出样例:

输入 #1:

5
5 7 1 2 10

输出 #1:

145
3 1 2 4 5

说明/提示:

数据规模与约定:

对于全部的测试点,保证 1≤n<30,节点的分数是小于 100 的正整数,答案不超过 4×10^9。

思路:

  如果你啥树的知识也不会,没关系,先跟我默念一遍:

   树 ,是递归定义的

此话怎讲?我们来看这个题的二叉树的积分规则

树的加分=左子树的加分× 的右子树的加分+根的分数

也就是说:树的加分,是由“左子树的加分、右子树的加分、根的分数”这三部分来决定的,那每一部分的加分又是怎么算的呢?

其中根的分数拿来用就行,而剩下两个,比如左子树的加分,不难发现左子树其实也是一棵树,它的积分规则也是由这三部分决定的

于是,我们又可以按着左子树的左子树和右子树算,就这样一环套一环,这有没有让你联想到什么呢?

没错,就是递归

那么递归总要有个头,不能一直下去没完了。那到什么时候才算结束呢?很简单,到叶节点的时候就结束啦!(不知道什么是叶节点的小伙伴注意啦:叶节点就是左右子树都空的节点,就像一棵树的叶子不会再往上长分枝了),递归到叶节点,直接返回该节点的加分,不用往下算啦!

到目前为止,怎么递归已经都想好了,那到底用什么算法,再具体点?

对啦,就是万能的深度优先搜索dfs!.


终于想好了思路,接下来终于要编代码啦!但是这是好多小伙伴肯定又犯了愁。正因为树这种数据结构的特殊性,我们应该怎么存储它呢???难道还需要开一个结构体吗?

悄悄告诉你,只需要一个数组就可以啦!

int n,a[40];//a来存储这棵树
cin>>n;
for(int i=1;i<=n;i++)
	cin>>a[i];

没错,我没骗你,就这么简单!

那可能各种问题又来了:我这么存储,怎样才能方便地获取每棵子树的信息呢?

别慌别慌,我们再看一下题目:输入的是这棵树的中序遍历,也就是左子树→根节点→右子树的遍历,我们可以设一个区间l~r,表示这棵树是a[l]到a[r]的部分,来看它是哪棵子树

for example,这棵树的中序遍历是:

5 7 1 2 10

那么1~5就觉得是整棵树,咱们假设中间的1为根节点,那根据中序遍历的顺序,根节点在中间,1左边的部分(5和7)也就是1~2,是左子树,右边的部分(2和10)也就是4~5,是右子树

也就是说,当整棵树为l~r,根结点为i时,左子树为l~i-1,右子树为i-1~r

懂了吧,那么再来几个!2~2是哪?

叶节点7!

那么5~4呢?

这个是一个空节点,并且,它的加分是1(题目都写了)

综上所述,我们总结到了三种情况:

  1. l<r时,1~r是指a[l]到a[r]的子树加分为左子树的加分× 的右子树的加分+根的分数
  2. l==r时,l~r是指叶节点a[l](或a[r],反正l==r嘛),加分为a[l]的值
  3. l>r时,l~r为空节点加分为1

于是我们就可以按这三条原则写出代码啦!在代码里,我们让每一个节点都当一次根节点,看看谁的最大

long long dfs(int l,int r){  //dfs函数,数据比较大,开long long保险 
	if(l>r)return 1;    //特殊情况1:如果为空节点,返回1 
	if(l==r)return a[l];   //特殊情况2:如果为叶节点,直接返回该节点的加分
   long long maxn=0;  //maxn来记录最大加分,作为最后的返回值
	for(int i=l;i<=r;i++){ 
		long long t=dfs(l,i-1)*dfs(i+1,r)+a[i];//t为以i为根节点的最大的加分 
		if(maxn<t) //更新最大值
			maxn=t;
    }
	return maxn;//返回最大值 
}

这样我们求最大加分的任务完成啦!但这还不够,这代码要递归这么多次,计算机这么可爱,怎么可以如此虐待计算机!重点是还会TLE,怎么办呢?

我们发现,我们在执行递归的过程中,同一棵子树算了一次又一次,会有很多重复的计算,何不把这些结果存起来,下次再用的时候直接拿过来就成! 好的,我们把代码改一下,由于加分是由l和r两个数决定的,我们就开一个名为dp的二维数组存吧!这次的代码,我们用dp[l][r]来记录l~r子树的最大加分,取代了maxn的位置

long long dp[40][40]={0};//dp[l][r]记录l~r子树的最大加分
long long dfs(int l,int r){   
	if(l>r)return 1;    
	if(l==r){           
		root[l][r]=l;
		return a[l];
	}
	if(dp[l][r])return dp[l][r];//特殊情况3:如果以前已经算过了,那就直接返回以前存起来的结果
	for(int i=l;i<=r;i++){ 
		long long t=dfs(l,i-1)*dfs(i+1,r)+a[i];
		if(dp[l][r]<t)
			dp[l][r]=t;
	}
	return dp[l][r];//返回最大值 
}

刚才的“如果以前已经算过了,那就直接返回以前存起来的结果”的思想,就是传说中的“记忆化搜索”,可以减少许多不必要的计算,再也不用担心我会TLE啦!


接下来的任务,就是输出最大加分的树的先序遍历。先序遍历的顺序是根节点→左子树→右子树,咱们还是用递归解决,定义一个名为root的二维数组,用来存储使l~r子树的加分最大的那个根节点,dfs咱都会编了,这种事那简直是a piece of cake!(小菜一碟)

void print(int l,int r){     //输出这棵树的先序遍历 
	if(l>r)return;      //如果节点为空(依然是l<r)结束 
	cout<<root[l][r]<<" ";  //先序遍历,先输出根节点 
	print(l,root[l][r]-1);  //然后左子树 
	print(root[l][r]+1,r);  //最后右子树 
}

注意题目让你输出的是节点的编号,也就是循环中的i而不是a[i],先把dfs函数里面加入储存root节点的代码,然后在print函数中按照先序遍历的顺序输出就完美解决了)


最后的最后,就是完整代码了!主程序应该很好编了吧!这里给大家介绍另一种处理叶节点的方法:由于它在dp数组和root数组中的值已经确定了,可以将它在主程序的循环中直接初始化最大加分和根结点位置。

完整代码:

#include<iostream> 
using namespace std;
int n,a[40],root[40][40];//a来存储中序遍历,root来存储最大积分的根节点 
long long dp[40][40]={0};//dp[l][r]记录从l区域到r区域最大的加分 
long long dfs(int l,int r){  //电风扇函数 
	if(l>r)return 1;    
	if(l==r){           //如果为叶节点,最大积分的根节点就是当前节点l 
		root[l][r]=l;
		return a[l];
	}
	if(dp[l][r])return dp[l][r];
	for(int i=l;i<=r;i++){ 
		long long t=dfs(l,i-1)*dfs(i+1,r)+a[i];
		if(dp[l][r]<t){ //更新最大值以及根节点位置 
			dp[l][r]=t;
			root[l][r]=i;
		}
	}
	return dp[l][r];
}
void print(int l,int r){     //输出这棵树的先序遍历 
	if(l>r)return;      //如果节点为空(依然是l<r)结束 
	cout<<root[l][r]<<" ";  //先序遍历,先输出根节点 
	print(l,root[l][r]-1);  //然后左子树 
	print(root[l][r]+1,r);  //最后右子树 
}
int main(){
	cin>>n;//输入
	for(int i=1;i<=n;i++){
		cin>>a[i];
		dp[i][i]=a[i];//叶节点的最大积分就是当前节点的积分,最大积分的根节点也是当前节点,直接给它初始化 
		root[i][i]=i;
	}
	cout<<dfs(1,n)<<endl;//从头到尾搜索 
	print(1,n);
	return 0; //树,是递归定义的 
}

 总结:

如果该题你认真思考的话,熟知树的结构,那是有很大几率可以做出来的。

祝大家早日AC!

The End~

 题目链接:

[NOIP2003 提高组] 加分二叉树 - 洛谷https://www.luogu.com.cn/problem/P1040

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值