AcWing 479. 加分二叉树(区间dp)

题目链接:点击这里

题目大意:
构造一颗 n n n 个节点的二叉树,其编号为 1 , 2 , 3 , … , n 1,2,3,…,n 1,2,3,,n ,每个节点都有一个权值 w i w_i wi
子树的权值 = = = 左子树权值 ∗ * 右子树权值 + + + 根节点权值
若某个子树为空,规定其权值为 1 1 1。叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历的节点编号为 1 , 2 , 3 , … , n 1,2,3,…,n 1,2,3,,n且加分最高的二叉树
输出其整棵树的最高分和此分数下字典序最小的前序遍历

题目分析:
因为此二叉树的中序遍历为 1 , 2 , 3 , . . . , n 1,2,3,...,n 1,2,3,...,n ,当我们确定根节点编号为 k k k 时,其左子树的节点编号一定为 1 1 1 k − 1 k-1 k1 ,右子树的节点编号一定为 k k k n n n ,是互不影响的,我们考虑区间 d p dp dp
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示编号为 i i i j j j 这一段构成的子树的最大权值
容易有转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ k − 1 ] ∗ d p [ k + 1 ] [ j ] + w [ k ] ) dp[i][j]=max(dp[i][k-1]*dp[k+1][j]+w[k]) dp[i][j]=max(dp[i][k1]dp[k+1][j]+w[k])
而此题还要求方案,且字典序最小,那么一定要有先转移 k k k 较小的情况,所以我们只需要边转移边记录,最后递归输出方案即可

具体细节见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
//#define int ll
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 105;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
struct node{
	int x,y;
}nod[maxn*maxn];
int n,w[maxn],dp[maxn][maxn],root[maxn][maxn];//root[i][j]表示dp[i][j]从哪里转移过来的 
void dfs(int l,int r)
{
	if(l > r) return ;
	int mid = root[l][r];
	printf("%d ",mid);
	dfs(l,mid-1);dfs(mid+1,r);
}
signed main()
{
	n = read();
	for(int i = 1;i <= n;i++) w[i] = read();
	for(int len = 1;len <= n;len++)
		for(int i = 1;i+len-1 <= n;i++)
		{
			int j = i+len-1;
			if(len == 1) dp[i][j] = w[i],root[i][j] = i;//特判叶子节点
			else {
				for(int k = i;k <= j;k++)
				{
					int l = k==i ? 1 : dp[i][k-1];
					int r = k==j ? 1 : dp[k+1][j];
					int tmp = l*r+w[k];
					if(tmp > dp[i][j]) dp[i][j] = tmp,root[i][j] = k;
				}
			} 
		} 
	printf("%d\n",dp[1][n]);
	dfs(1,n);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值