[NOIP2003]加分二叉树 --动态规划-类似矩阵相乘

http://www.rqnoj.cn/Problem_49.html


题目:[NOIP2003]加分二叉树

问题编号:49 

题目描述

设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
若某个子树为主,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空
子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历

输入格式

第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。

输出格式

第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。

样例输入

5
5 7 1 2 10

样例输出

145
3 1 2 4 5


一、思路

构造最优解,描述结构

分解子问题,写出递推式

求解


想法1:新加的一个节点 如何安排,它可能在单子叔的最左端,也可能在右子树的最右端

也可能因为这个点的加入导致原先的最大加分叔发生了变化

    这个思路不行


想法2:参考以前的动态规划,题目,想想对于给定的n,那么其最大加分树

必然是以i,1<=i<=n 未根节点 而i一个有1-n种取值

然后已1-i-1 构成最大加分左子树,以i+1,n构成最大加分右子树

否则会存在比原最大加分叔更优的最大加分树,显然矛盾

这样就符合了最优子结构的性质


当i=4时 和 i=5时显然都要求 1  2 3 的最大加分左子树

所以子问题重叠


那用结构什么来定义描述,这个结构?一维二维还是什么?

如果用f[n]来描述当n个节点时的最大加分树的最大加分


递归的最底端是 一个个单独节点

两个节点的时候,要用左侧充当次左子树,右侧充当次右子树

三个节点的时候,会有 1 、2 , 1、1、1,2、1这三种

显然一维的不行 需要f[i][j]这种描述方式


和矩阵相乘一样的意思了

f[i][j] = f[i][root-1] * f[root+1][j] + value[root];   i<=root<=j


这样问题可解了

==================

如何前序输出?

显然用递归

在求f[i][j]的时候都会找其最大加分树的根,那么就又用个数组存起这个根,这样省的每次都去逆过程匹配找这个根

输出的时候,先输出root[1][n],然后再输出其最大加分左子树的前序序列,再输出其最大加分右子树的前序序列即可


二、算法

用value[]来保存每个点的分数

用f[31][31]来保存序列f[i][j]的最大加分

用root[31][31]来保存序列root[i][j]的根节点下标


1、读入n

2、读入v

3、初始条件

初始化:

f[i][i] = value[i]

root[i][i]  = i


4、三重循环动态规划求解

   一重:k,子序列宽度          从2到n 

  二重:i,子序列起始位置  从1到 n-k+1 如 4 5  4= 5 -2 + 1

 三重:j,子序列的根节点可能位置 从i 到 i+k-1 如 1 2 k=2 i=1 j=1到i+k-1 = 2

注意:如果当i=1,j=i时, 左子树为空即f[i][j-1] 越界取值1

所以有 

int left = i<=j-1 ? f[i][j-1] : 1;
int right = j+1<=i+k-1 ? f[j+1][i+k-1] : 1;


4、输出f[1][n]

递归输出root,1,n

注意事项:

考虑到输出格式要求3 1 2 4 5

3前面没有空格


所以就先输出root[1][n]

再输出其左子树的前序

再输出其右子树的前序


在递归输出的时候,都加了个空格 

cout<<' '<<root[begin][end];


AC了

代码如下:

#include <iostream.h>
#include <fstream.h>

void PrintRoot(int root[31][31], int begin, int end)
{
	if (begin > end)
	{
		return ;
	}
	cout<<' '<<root[begin][end];
	PrintRoot(root, begin, root[begin][end]-1);
	PrintRoot(root, root[begin][end]+1, end);
}

int main()
{
	int n, value[31], i, j, k;
	ifstream inFile("e:\\test.txt");
	int f[31][31]={0};
	int root[31][31]={0};//保存根节点

	//读入节点数
	cin>>n;
	//inFile>>n;
	//读入节点价值
	for (i=1; i<=n; i++)
	{
		cin>>value[i];
		//inFile>>value[i];
	}
	//动态规划求解
	//初始条件,单个节点加分为节点本身的分数
	for (i=1; i<=n; i++)
	{
		f[i][i] = value[i];
		root[i][i] = i;
	}
	//宽度从1到n
	for (k=2; k<=n; k++)
	{
		for (i=1; i<=n-k+1; i++)
		{
			f[i][i+k-1] = -9999999;
			for (j=i; j<=i+k-1; j++)
			{
				int left = i<=j-1 ? f[i][j-1] : 1;
				int right = j+1<=i+k-1 ? f[j+1][i+k-1] : 1;
				int sum = left*right + value[j];				
				root[i][i+k-1] = f[i][i+k-1]<sum ? j : root[i][i+k-1];
				f[i][i+k-1] = f[i][i+k-1]<sum ? sum : f[i][i+k-1];
			}
		}
	}
	//输出结果
	cout<<f[1][n];
	cout<<endl;
	cout<<root[1][n];
	PrintRoot(root, 1, root[1][n]-1);
	PrintRoot(root, root[1][n]+1, n);



	inFile.close();
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值