http://www.rqnoj.cn/Problem_49.html
题目:[NOIP2003]加分二叉树
题目描述
设一个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个用空格隔开的整数,为该树的前序遍历。
样例输入
55 7 1 2 10
样例输出
1453 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;
}