【NOIP2013提高组T3】加分二叉树

题目描述

设一个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行:一个整数n(n<30),为节点个数。

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

输出格式:

第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。

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

输入输出样例

输入样例#1

5

5 7 1 2 10

输出样例#1

145

3 1 2 4 5

 

 

算法:

区间DP

 

分析:

这个10多年前的提高组试题其实也不算太难,但是有很多要注意的小点。

首先这道题上手先分析感觉和树形DP有点关系,然而再看清楚一点呢,就发现它其实只是在区间上dp,然而树只是对它的一个约束。

我们先来简化题目,假如我问的是在一个区间上求最大加分,那么这个状态转移方程应该很容易得到,就是f[i][j]=max(f[i][k-1]*f[k+1][j]+a[k])其中表示从i到j的区间最大值,k满足i<=k<=j。

注意这里可以取等。

 

接下来,有一个性质:

对于任意二叉树,其中序遍历中的任意一段区间的根节点可以是任何一个节点。

 

那么问题解决。

 

这个就可以直接套用到区间DP中,不过还要记录下相应的i,j的根节点是什么。最后输出先序遍历的过程其实就是dfs的思想,加一个记忆化搜索会提高效率。

 

要点注意:

1、为了方便调试,可以将数组开到6,但注意要调回正常值在提交

2、存答案的数组要用long long,否则会wa

3、记忆化部分要确定有值(非空子树)才递归下去

4、初始化答案数组f要放在输入前

5、注意要先定好某次递推的区间长度,否则会找到没运算过的值。

 

 

上代码:

 

 

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 
 5 long long f[35][35];                            //注意要用超长整型
 6 int n,r[35][35],a[35],fl=0;
 7 
 8 inline int read()
 9 {
10     int x=0,f=1;
11     char c=getchar();
12     while (c<48||c>57)
13         f=c=='-'?-1:1,c=getchar();
14     while (c>=48&&c<=57)
15         x=(x<<1)+(x<<3)+(c^48),c=getchar();
16     return x*f;
17 }
18 
19 void tree(int lt,int rt)
20 {
21     if (r[lt][rt])                            //某些oj会判结尾为空格的情况
22     {
23         if (!fl)
24             printf("%d",r[lt][rt]),fl=1;
25         else
26             printf(" %d",r[lt][rt]);
27     }
28     if (r[lt][r[lt][rt]-1])                        //递归左子树
29         tree(lt,r[lt][rt]-1);
30     if (r[r[lt][rt]+1][rt])                        //递归右子树
31         tree(r[lt][rt]+1,rt);
32 }
33 
34 int main()
35 {
36     int i,j,k,len;
37     n=read();
38     for (i=0;i<=n;i++)                        //初始化为1,注意不可以memset
39         for (j=0;j<=n;j++)
40             f[i][j]=1;
41     for (i=1;i<=n;i++)
42     {
43         a[i]=read();
44         f[i][i]=a[i];                        //每个点自己作为叶子节点时,
45         r[i][i]=i;                            //根节点就是自己
46     }
47     /*                                //注意这种方法不可取
48     for (i=1;i<=n-1;i++)
49         for (j=i+1;j<=n;j++)
50         {
51             long long tmp=INF;
52             for (k=i;k<=j;k++)
53                 if (tmp<f[i][k-1]*f[k+1][j]+a[k])
54                 {
55                     tmp=f[i][k-1]*f[k+1][j]+a[k];
56                     r[i][j]=k;
57                 }
58             f[i][j]=tmp;
59         }
60     */
61     for (len=1;len<=n;len++)                        //先定区间长度
62         for (i=1;i+len<=n;i++)                    //设起点,i+len为终点
63         {
64             long long tmp=-1;                    //没必要太小
65             for (k=i;k<=i+len;k++)                //寻根
66                 if (tmp<f[i][k-1]*f[k+1][i+len]+a[k])
67                 {
68                     tmp=f[i][k-1]*f[k+1][i+len]+a[k];
69                     r[i][i+len]=k;
70                 }
71             f[i][i+len]=tmp;
72         }
73     printf("%lld\n",f[1][n]);
74     tree(1,n);
75     return 0;
76 }

 

 

 

讲讲memset为什么不行,因为在c++中memset是按位来赋值的,一个int是4位,一个long long是8位(好像是吧),所以一次就会推了8个1,而不是想要的一个1。而对于清零和赋极值memset是很好用的。

 

嗯,就这样了。

 

posted @ 2018-02-12 18:56  MN2016  阅读(218)  评论(0编辑  收藏  举报