1. 程式人生 > >洛谷【P1404】加分二叉樹

洛谷【P1404】加分二叉樹

題目描述

設一個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,但本題能用區間DP解決。題目給出二叉樹的中序遍歷為1~n,那麼包含在[1,n]內的任意一個區間[l,r]都可以代表一棵子樹,根節點為[l,r]內的任意一個數。根據題目中給出的二叉樹加分的計算方法,可以寫出如下的轉移方程:

狀態設計:F[i][j]表示區間[i,j]代表子樹的加分

狀態轉移:F[i][j]=max{F[i][k-1]*F[k+1][j]+V[k]}(i<=k<=j,V[k]代表k節點的加分)

題目要求葉子節點的加分為節點本身的分數,空子樹的加分為1,因此邊界條件處理如下:

邊界處理:F[i][i]=V[i],F[i][i+1]=1

至於列印答案的問題,有兩種方法。一是列舉根節點,看哪一個符合狀態轉移方程;二是用空間換時間,開一個數組G,用G[l][r]代表區間[l,r]中的根節點。初始時G[i][i]=i

程式碼

#include <iostream>
#include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int maxn = 33; int v[maxn],g[maxn][maxn]; LL f[maxn][maxn]; int n; void dfs(int l,int r) { if(l>r) return; int t=g[l][r]; printf("%d ",t); dfs(l,t-1); dfs(t+1,r); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&v[i]); for(int i=0;i<=n;i++) f[i+1][i]=1;//空子樹的初始化從0開始 for(int i=1;i<=n;i++) f[i][i]=v[i],g[i][i]=i; for(int l=1;l<n;l++)//先列舉區間長度,移動一段固定的區間進行轉移 { for(int i=1;i<=n-l;i++) { int j=i+l; for(int k=i;k<=j;k++) { LL t=f[i][k-1]*f[k+1][j]+v[k]; if(t>f[i][j]) { f[i][j]=t; g[i][j]=k; } } } } printf("%lld\n",f[1][n]); dfs(1,n); return 0; }