【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是很好用的。
嗯,就这样了。