NuptOJ1039加分二叉树——树形动态规划

这是一篇关于树形动态规划的博客,详细介绍了如何解决NOIP2003竞赛中的一道加分二叉树问题。博主探讨了如何通过动态规划找到加分最高的二叉树,并给出了动态规划的状态转移方程,以及处理特殊情况的方法。同时,提到了在输出前序遍历时的注意事项和一些编程技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

加分二叉树

时间限制(普通/Java):1000MS/3000MS          运行内存限制:65536KByte
总提交:105            测试通过:33

描述

设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为dj,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个用空格隔开的整数,为该树的前序遍历。

样例输入

5
5 7 1 2 10

样例输出

145
3 1 2 4 5

题目来源

NOIP2003


分析:已吐  orz

连着2道DP,刚不住了~ T_T

非常精妙的树形DP。

     3
   /    \

  1     4
    \      \
     2     5

设节点d为最优的根节点,那么可以把这棵树分成[1,d-1]和[d+1,n],这颗树的加分为子树[1,d-1]的加分与子树[d+1,n]加分的乘积与d的加分的和,而[1,d-1]和[d+1,n]的加分也可也一定是最优加分,所以这个题具有最优子结构。

那么可以用动态规划   

设f[k,j]为子树k到j的最高加分,求f[k,j]的最优值,就要求f[1,d-1]和f[d+1,n]的最优加分,那么枚举根节点p,则有      

f[k,j]的最优值=f[k,p-1]*f[p+1,j]+v[p](k<p<j)  (v[p]为p节点的分数)

规划方程为f[k,j]=max{f[k,p-1]*f[p+1,j]+v[p]}(k<p<j)  

但是,根据题目,左子树或右子树可以为空,即当k=p或p=j,于是对此特殊处理,求出这种情况下的加分,再进行比较  

动态规划顺序:根据规划方程,要求f[i,j]要先求解出f[i,k-1]和f[k+1,j],那么可以根据j-k从小到大的顺序动态规划,其实就是要求一棵树的加分,先要求它的子树加分,根据树的大小作为规划顺序  

打印前序遍历:       前序遍历的顺序为根->左子树->右子树       可以在程序中每一次更新当前子树的加分时,用tree[]记录此时的子树的根节点,那么最后纪录的就是最优的二叉树的节点       打印时,先找出f[1,n]的根节点p,打印,然后分成f[1,p-1]和f[p+1,n]递归打印,直到打印完叶子节点。

精妙的地方在于:由于是中序遍历,节点是相邻的,非常方便操作。

注意点:

若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。这句话要好好体会,这个就和上一个stone(最小代价生成树那道题)的动态规划不一样。那道题直接拉上来比较,且只要比较一次。而这道题由于当某个子树为空时,用程序的写法相当于乘以1。所以还是有区别的。

②输出格式有个坑。中序遍历输出中间用空格隔开且最后没有空格。其实还蛮有难度的。学习别人的代码,空格输出在前面,而不是跟在后面,这样最后一个元素不用跟着输出一个空格了。用了一个bool变量来判断是否为输出的第一个,这样只有第一个前面没有空格,其余元素前面都加一个空格。

写法有多种!DP思想还需体会!


方法一:沿用上一题最小生成树的风格!

#include<stdio.h>
#include<string.h>

//树形动态规划

int dp[30][30], tree[30][30];
bool isFirst; // 输出空格

void print(int l, int r)
{
	if(l > r)
		return ;
	if(isFirst)
		isFirst = false;
	else
		printf(" ");
	printf("%d",tree[l][r]);
	print(l, tree[l][r]-1);
	print(tree[l][r]+1, r);
}
int main()
{
	int n;
	while(scanf("%d",&n) != EOF)
	{
		memset(dp, 0 ,sizeof(dp));
		memset(tree, 0, sizeof(tree));
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&dp[i][i]); 
			tree[i][i] = i;
		}
		
		for(int i=1;i<=n-1;i++)
		{
			dp[i][i+1] = dp[i][i] + dp[i+1][i+1];
			tree[i][i+1] = i;
		}

		for(int d=2;d<n;d++)  
		{  
			for(int i=1,j=i+d;j<=n;i++,j++)  
			{ 
				for(int k=i+1;k<j;k++)
				{
					if(dp[i][j] < dp[i][k-1]*dp[k+1][j]+dp[k][k])
					{
						dp[i][j] = dp[i][k-1]*dp[k+1][j]+dp[k][k];
						tree[i][j] = k;
					}
					if(dp[i][j] < dp[i][j-1] + dp[j][j]) // 左子树为空
					{
						dp[i][j] = dp[i][j-1] + dp[j][j];
						tree[i][j] = j;
					}
					if(dp[i][j] < dp[i+1][j] + dp[i][i]) // 右子树为空
					{
						dp[i][j] = dp[i+1][j] + dp[i][i];
						tree[i][j] = i;
					}
				}
			}
		}
		printf("%d\n",dp[1][n]);
		isFirst = true;
		print(1, n);
	}
	return 0;
}

方法二:DP中递归

#include<iostream>
#include<cstring>
using namespace std;

int n;
long dp[40][40];
int tree[40][40];
bool isFirst;
void visit(int i,int j) // 当中序遍历为1,2,3,4,5。。。时,前序遍历的求法。 
{
	if(i>j)
		return ;
	if(isFirst)
		isFirst=false;
	else 
		cout<<" ";
	cout<<tree[i][j];
	visit(i,tree[i][j]-1);
	visit(tree[i][j]+1,j);

} 
int Search_dp(int l,int r)
{
	int i;
	int now;
	if(l>r)  return 1;
	else
	{
		if(dp[l][r] == -1)
		{
			for(i=l;i<=r;i++)
			{
				now = Search_dp(l,i-1)*Search_dp(i+1,r)+dp[i][i];
				if(now > dp[l][r])
				{
					dp[l][r] = now;
					tree[l][r] = i;
				}    
			}    
		}    
		return dp[l][r];
	}    
}    
int main()
{
	while(cin>>n)
	{
		memset(dp,-1,sizeof(dp));
		memset(tree,-1,sizeof(tree));
		for(int i=1;i<=n;i++)
		{ 
			cin>>dp[i][i];
			tree[i][i] = i;
		}
		cout<<Search_dp(1,n)<<endl;
		isFirst = true;
		visit(1,n);
		cout<<endl;    
	}      
	return 0;
}     



                    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值