[区间dp] 加分二叉树

题目描述

设一个 n n n 个节点的二叉树 tree \text{tree} tree 的中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,,n),其中数字 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i i i 个节点的分数为 d i d_i di tree \text{tree} tree 及它的每个子树都有一个加分,任一棵子树 subtree \text{subtree} subtree(也包含 tree \text{tree} tree 本身)的加分计算方法如下:

subtree \text{subtree} subtree 的左子树的加分 × \times × subtree \text{subtree} subtree 的右子树的加分 + + + subtree \text{subtree} subtree 的根的分数。

若某个子树为空,规定其加分为 1 1 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,,n) 且加分最高的二叉树 tree \text{tree} tree。要求输出

  1. tree \text{tree} tree 的最高加分。

  2. tree \text{tree} tree 的前序遍历。

输入格式

1 1 1 1 1 1 个整数 n n n,为节点个数。

2 2 2 n n n 个用空格隔开的整数,为每个节点的分数

输出格式

1 1 1 1 1 1 个整数,为最高加分($ Ans \le 4,000,000,000$)。

2 2 2 n n n 个用空格隔开的整数,为该树的前序遍历。

样例

样例输入1:

5
5 7 1 2 10

样例输出1:

145
3 1 2 4 5

数据范围

对于全部的测试点,保证 1 ≤ n < 30 1 \le n< 30 1n<30,节点的分数是小于 100 100 100 的正整数,答案不超过 4 × 1 0 9 4 \times 10^9 4×109

题解

观察到 n ≤ 30 n \le 30 n30,求最高加分,因此考虑到 DP。

f i , j f_{i, j} fi,j 表示 i i i j j j 的最大加分。

转移时枚举中转点 k k k,把 k k k 当作根,计算左右子树的加分的乘积和该节点的加分之和,即 f i , j = f i , k − 1 × f k + 1 , j + a i f_{i, j} = f_{i, k - 1} \times f_{k + 1, j} + a_i fi,j=fi,k1×fk+1,j+ai。注意左右子树为空的情况要特判。

如何求这棵树呢?

在转移时,如果能更新 f i , j f_{i, j} fi,j,就用一个 d d d 数组记录区间 [ i , j ] [i, j] [i,j] 的根(也可以记录 k − i k - i ki)。输出方案时,进行 dfs。dfs 记录区间 [ l , r ] [l, r] [l,r],每次找出根 t t t 输出,然后继续递归 [ l , t − 1 ] [l, t - 1] [l,t1] [ t + 1 , r ] [t + 1, r] [t+1,r]

#include<bits/stdc++.h>
using namespace std;
int n;
int a[40];
int f[40][40];
int d[40][40];
void dfs(int x, int y){//输出方案
	if(x > y){
		return;
	}
	printf("%d ", x + d[x][y]);
	dfs(x, x + d[x][y] - 1);
	dfs(x + d[x][y] + 1, y);
}
int main(){
	memset(f, 0, sizeof(f));
	memset(d, 0, sizeof(d));
	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];
		d[i][i] = 0;
	}
	for(int k = 2; k <= n; ++ k){
		for(int i = 1; i <= n - k + 1; ++ i){
			int j = i + k - 1, t1, t2;
			for(int u = i; u <= j; ++ u){
				if(u == i){
					t1 = 1;
				}
				else{
					t1 = f[i][u - 1];
				}
				if(u == j){
					t2 = 1;
				}
				else{
					t2 = f[u + 1][j];
				}
				int t = t1 * t2 + a[u];
				if(f[i][j] < t){
					f[i][j] = t;
					d[i][j] = u - i;
				}
			}
		}
	}
	printf("%d\n", f[1][n]);
	dfs(1, n);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值