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;
}
相關文章
相關標籤/搜索