1. 程式人生 > >洛谷P1040 加分二叉樹

洛谷P1040 加分二叉樹

題目描述

設一個 n 個節點的二叉樹tree的中序遍歷為($1,2,3,…,n$),其中數字$1,2,3,…,n$為節點編號。每個節點都有一個分數(均為正整數),記第 i 個節點的分數為$di,tree$及它的每個子樹都有一個加分,任一棵子樹$subtree$(也包含$tree$本身)的加分計算方法如下:

$subtree$的左子樹的加分× subtreesubtreesubtree的右子樹的加分+$subtree$的根的分數。

若某個子樹為空,規定其加分為$1$,葉子的加分就是葉節點本身的分數。不考慮它的空子樹。

試求一棵符合中序遍歷為($1,2,3,…,n$)且加分最高的二叉樹$tree$。要求輸出;

(1)$tree$的最高加分

(2)$tree$的前序遍歷

輸入輸出格式

輸入格式:

第$1$行:$1$個整數$n(n<30)$,為節點個數。

第$2$行:$n$個用空格隔開的整數,為每個節點的分數(分數$ <100$)。

輸出格式:

第$1$行:$1$個整數,為最高加分(Ans ≤4,000,000,000 \le 4,000,000,000≤4,000,000,000)。

第$2$行:$n$個用空格隔開的整數,為該樹的前序遍歷。

 

輸入輸出樣例

輸入樣例#1:

5
5 7 1 2 10

輸出樣例#1:

145
3 1 2 4 5

 這是一道樹形dp(幻燈片太惡意了。。。)
中序排列 = 左子樹 + 根節點 +  右子樹
然後我們就可以對於每個從i到j的節點的子樹列舉它的根節點k,不斷更新最大值同時把用g陣列儲存k值

一共有三種情況,空子樹,葉結點和正常點,所以我們可以列出一個式子(你們想象一下有個大括號(我打不出來))

                a[i]      i=j(葉子)

•f [i,j]=       1        i=j+1(空子樹)                               

                 max{f [i,k-1]*f [k+1,j]+a[k]} i<=k<=j

答案為f [1,n],時間複雜度為o(n3)。

接下來用遞迴輸出前序排列就行了
然後又ac了(鼓掌.jpg)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int n;
int f[33][33],g[33][33],a[33];
const int q = 1e5 + 3;
int print(int l,int r)
{
	if(l > r)return 0;
	if(l == r)printf("%d ",l);//到了葉結點就把它直接輸出
	else{
		printf("%d ",g[l][r]);//輸出根節點 
		print(l,g[l][r] - 1);//繼續去遍歷左子樹 
		print(g[l][r] + 1,r);//遍歷右子樹 
	}
}
int main()
{
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)scanf("%d",&a[i]);
	for(int i = 1;i <= n;i++)
	{
		f[i][i] = a[i];//相當於葉結點 
		f[i][i - 1] = 1;//相當於空子樹 
	}
	for(int l = 2;l <= n;l++)
	{
		for(int i = 1;i <= n;i++)
		{
			int j = i + l - 1;
			f[i][j] = -q;//初始化這個陣列的值為一個負數,這樣就可以確保沒有被計算過的點不會被選中 
			for(int k = i;k <= j;k++)
			{
				if(f[i][k - 1] * f[k + 1][j] + a[k] > f[i][j])
				{
					f[i][j] = max(f[i][j],f[i][k - 1] * f[k + 1][j] + a[k]); 
					g[i][j] = k;//儲存從i到j的根節點k 
				}
			}
		}
	}
	printf("%d\n",f[1][n]);
	print(1,n);
	return 0;
}