原题连接ios
题目所求是一棵符合中序遍历且加分最高的二叉树, 而二叉树的加分 \(=\) 左子树的加分 \(×\) 右子树的加分 \(+\) 根的分数
假设求一棵根节点是\(k\)的加分最高的二叉树,因为根的分数已经肯定,则要使其左子树加分最高且右子树加分最高
如何使其左子树加分最高呢?(右子树同理)
首先要在\([1,k-1]\)(为何是\([1,k-1]?\) 由于在中序序列中, 根节点左边是其左子树, 右边是其右子树)枚举左子树的根\(j\), 且使以\(j\)为根节点的二叉树的左子树加分最高且右子树加分最高.
因此求二叉树的最高加分实际上是一个不断进行递归的过程, 能够用区间DP
来解决
区间DP
思路以下:ide
题目还让求一个加分最高的二叉树的前序遍历, 且字典序最小
如何求前序遍历? 在区间DP
的过程当中,每一次从\([i,j]\)划分出的若干类中选出的最优的第\(k\)类, 其实就能够肯定\([i,j]\)的根节点是\(k\), 再肯定其左右子树的根节点, 从而递归求出前序遍历
如何保证字典序最小? 注意到求前序遍历的过程其实是选择根节点的过程, 则保证每次选出的根节点最小便可spa
看不懂就参考Y总视频讲解code
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 35; int n; int a[N]; int f[N][N]; // f[i][j] 表示 中序遍历是[i,j]全部二叉树的集合 的最大加分 int root[N][N]; // 记录[i,j]的根节点 void dfs(int l, int r) { if(l > r) return; int k = root[l][r]; cout << k << " "; dfs(l,k-1); dfs(k+1,r); return; } int main() { cin >> n; for(int i=1; i<=n; i++) cin >> a[i]; for(int len = 1; len <= n; len++) // 枚举区间长度 { for(int i=1; i + len - 1 <= n; i++) // 枚举区间左端点 { int j = i + len - 1; // 肯定区间右端点 for(int k=i; k<=j; k++ ) // 枚举根节点 { int left_score = k==i ? 1 : f[i][k-1]; // 肯定左子树的加分(当左子树为空(k == i), 规定加分为1) int right_score = k==j ? 1 : f[k+1][j]; // 肯定右子树的加分 // 肯定以K为根节点的二叉树的加分,叶子(i==j)的加分就是叶节点自己的分数,不考虑它的空子树。 int k_score = i == j ? a[k] : left_score * right_score + a[k]; if(f[i][j] < k_score) { f[i][j] = k_score; root[i][j] = k; } } } } cout << f[1][n] << endl; dfs(1,n); // 输出前序序列 system("pause"); return 0; }