[NOIP2003 提高组] 加分二叉树

这篇具有很好参考价值的文章主要介绍了[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[NOIP2003 提高组] 加分二叉树文章来源地址https://www.toymoban.com/news/detail-476355.html

到了这里,关于[NOIP2003 提高组] 加分二叉树的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 加分二叉树

    设一个 (n) 个节点的二叉树 (text{tree}) 的中序遍历为 ((1,2,3,ldots,n)) ,其中数字 (1,2,3,ldots,n) 为节点编号。每个节点都有一个分数(均为正整数),记第 (i) 个节点的分数为 (d_i) , (text{tree}) 及它的每个子树都有一个加分,任一棵子树 (text{subtree}) (也包含 (tex

    2024年02月06日
    浏览(34)
  • C++---区间DP---加分二叉树(每日一道算法2023.4.28)

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

    2024年02月06日
    浏览(32)
  • 二叉树题目:对称二叉树

    标题:对称二叉树 出处:101. 对称二叉树 3 级 要求 给你一个二叉树的根结点 root texttt{root} root ,检查它是否轴对称。 示例 示例 1: 输入: root   =   [1,2,2,3,4,4,3] texttt{root = [1,2,2,3,4,4,3]} root = [1,2,2,3,4,4,3] 输出: true texttt{true} true 示例 2: 输入: root   =   [1,2,2,null,3,null,

    2024年02月12日
    浏览(34)
  • 二叉树题目:二叉树的中序遍历

    标题:二叉树的中序遍历 出处:94. 二叉树的中序遍历 3 级 要求 给你二叉树的根结点 root texttt{root} root ,返回其结点值的中序遍历。 示例 示例 1: 输入: root   =   [1,null,2,3] texttt{root = [1,null,2,3]} root = [1,null,2,3] 输出: [1,3,2] texttt{[1,3,2]} [1,3,2] 示例 2: 输入: root   =  

    2024年02月09日
    浏览(50)
  • 二叉树题目:在二叉树中增加一行

    标题:在二叉树中增加一行 出处:623. 在二叉树中增加一行 5 级 要求 给定一个二叉树的根结点 root texttt{root} root 和两个整数 val texttt{val} val 和 depth texttt{depth} depth ,在给定的深度 depth texttt{depth} depth 处添加一行值为 val texttt{val} val 的结点。 注意,根结点 root texttt{root}

    2024年02月06日
    浏览(32)
  • 二叉树题目:二叉树的层序遍历 II

    标题:二叉树的层序遍历 II 出处:107. 二叉树的层序遍历 II 4 级 要求 给你二叉树的根结点 root texttt{root} root ,返回其结点值自底向上的层序遍历(即从左到右,按从叶结点所在层到根结点所在层逐层遍历)。 示例 示例 1: 输入: root   =   [3,9,20,null,null,15,7] texttt{root = [3

    2024年02月11日
    浏览(35)
  • 【二叉树复习】C++ 二叉树复习及题目解析 (1)

    目录 二叉树 树 相关概念 树的表示 二叉树 概念 存储结构 小练习 树题目: leetcode 965 单值二叉树。 leetcode 103. 二叉树的最大深度 leetcode 226 翻转二叉树 leetcode100 相同的树 leetcode 101 对称二叉树 leetcode 144前序遍历 94 中序遍历 145 后序遍历 leetcode 572 另一棵树的子树 本文将从二

    2024年02月12日
    浏览(32)
  • 二叉树经典算法题目

    省略 输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。 例如: 给定二叉树 [3,9,20,null,null,15,7] , 返回它的最大深度 3 。 思路:递归,当前数的深度等于左子数和右子树其中最大深

    2024年02月09日
    浏览(56)
  • 二叉树题目合集(C++)

    链接: 二叉树创建字符串 题目要求: PS :题目描述的不是特别清楚,其实就是 前序遍历 树,然后 用括号分别包含左子树和右子树遍历结果 。 基础思路: (1)不考虑括号去重 的话,其实只要 访问完当前节点后递归访问左右子树 即可,并且在访问前加左括号,访问完毕后

    2024年02月07日
    浏览(31)
  • 二叉树基础oj题目

    前文中,介绍了二叉树的基本概念及基础操作,进一步对于二叉树的递归遍历及子问题的处理思想有了一定的了解。本文将带来几道二叉树经典的oj题目。 对称二叉树 平衡二叉树 二叉树的层序遍历 leetcode题目链接 题目描述:给你一个二叉树的根节点 root , 检查它是否轴对称

    2024年01月21日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包