Time Limit:1000 ms
Memory Limit:65536 K
设一个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的前序遍历
现在,请你帮助你的好朋友XZ设计一个程序,求得正确的答案。
第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
n(n<=30)
分数<=100
算法讨论
本题一看题目描述就知道这题必须要建立一个树的数据结构,而最后要让求最大值,那么就只能用动态规划了,因为如果用深搜之类的肯定是浪费时间太多,每个点枚举一边,额…时间是问题。
那么我们已经确定是树形动态规划了,就需要建立一棵树,那么题目让求最大值就说明一定要枚举每一个点当root,因为输入是中序遍历,所以假设枚举的一个根是3号点,那么他的左子树是1号2号点,右子树是4..n号点,那么这给点的最优值是1号点或二号点乘上4号..或n号+上本身,那么左子树的点和右子树的点又得枚举了,然后递归知道还有最后一个点的时候。要注意的是判定边界和记忆化的问题,记忆化可以使你的代码更快,每一棵树少算一遍。
那么既然是动态规划,就要有状态转移方程了,每个点的最优值就等于左子树点的最优值乘上右子树点的最优值+本身,这就是f数组。
状态转移方程:f[r,l]:=max{f[i,k] * f[k,j]+a[i]}(for k:=r to l do)
Pascal代码:
var
a:array[0..1000] of longint; {存下每一个点的分数}
f:array[0..1000,0..1001] of int64; {存下最优分数,因为数据原因所以用int64,中括号里存左儿子点和右儿子点}
root:array[0..1000,0..1000] of longint; {最后为了输出的问题,还需要先序遍历,所以存一下根,左子树点右子树点}
n,i:longint;
function treedp(l,r:longint):int64; {递归函数,把上一次递归的边带进来了}
var
i:longint;
max,t:int64;
begin
max:=0; {要把每一个子树的最大值存一下}
if f[l,r]>=0 then exit(f[l,r]); {记忆化,算过了就不要再算了直接用}
if l=r then
begin
f[l,r]:=a[l];
root[l,r]:=l;
exit(f[l,r]);
end; {边界处理问题,别出边!}
if l>r then
begin
f[l,r]:=1;
exit(f[l,r]);
end; {这个点没有右兄弟,所以分是1}
for i:=l to r do {枚举第i个点为根}
begin
t:=treedp(l,i-1)*treedp(i+1,r)+a[i]; {递归进去}
if t>max then
begin
max:=t;
root[l,r]:=i;
end; {状态转移方程,如果这个点的值大于最大那么替换,还要把根的位置存一下}
end;
f[l,r]:=max; {把最大存入f数组}
exit(f[l,r]); {结束递归}
end;
procedure qianxu(l,r:longint); {前序遍历过程}
begin
if l>r then exit;
if (l=1) and (r=n) then
write(root[l,r])
else
write(' ',root[l,r]);
qianxu(l,root[l,r]-1);
qianxu(root[l,r]+1,r);
end;
begin
readln(n);
for i:=1 to n do read(a[i]);
fillchar(f,sizeof(f),255); {先把f初始化成-1,这是为了记忆化}
writeln(treedp(1,n));
qianxu(1,n);
writeln;
end.