AcWing 479. 加分二叉树 (区间DP)
题目描述
分析
题目所求是一棵符合中序遍历且加分最高的二叉树, 而二叉树的加分 \(=\) 左子树的加分 \(×\) 右子树的加分 \(+\) 根的分数
假设求一棵根节点是\(k\)的加分最高的二叉树,由于根的分数已经确定,则要使其左子树加分最高且右子树加分最高
如何使其左子树加分最高呢?(右子树同理)
首先要在\([1,k-1]\)(为什么是\([1,k-1]?\) 因为在中序序列中, 根节点左边是其左子树, 右边是其右子树)枚举左子树的根\(j\), 且使以\(j\)为根节点的二叉树的左子树加分最高且右子树加分最高.
所以求二叉树的最高加分其实是一个不断进行递归的过程, 可以用区间DP来解决
区间DP思路如下:
题目还让求一个加分最高的二叉树的前序遍历, 且字典序最小
如何求前序遍历? 在区间DP的过程中,每一次从\([i,j]\)划分出的若干类中选出的最优的第\(k\)类, 其实就可以确定\([i,j]\)的根节点是\(k\), 再确定其左右子树的根节点, 从而递归求出前序遍历
如何保证字典序最小? 注意到求前序遍历的过程实际上是选择根节点的过程, 则保证每次选出的根节点最小即可
看不懂就参考Y总视频讲解
实现
#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;
}

浙公网安备 33010602011771号