AcWing 479. 加分二叉树 (区间DP)

题目描述

原题连接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;
}
相关文章
相关标签/搜索