洛谷【P1404】加分二叉樹
阿新 • • 發佈:2019-02-15
題目描述
設一個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;
}