1580 加分二叉树(NOIP2003 LOJ10158 LUOGU1040 普及+/提高) 与中序遍历匹配的树上区间动规

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

弄明白样例是关键。

由 中序遍历 ,前序遍历 确定样例,二叉树形态。

样例分数计算如下:

f(2)=7
f(5)=1*7+5=12

f(5)=10
f(4)=1*10+2=12

f(3)=12*12+1=145

 节点数n=5,二叉树,形态数量,计算过程如下:

f(0)=1
f(1)=1

节点数量为2,扣除根节点后,还剩1个节点,该节点在左子树,对应形态数量f(1)*f(0)=1,
该节点在右子树,对应形态数量f(0)*f(1)=1
f(2)=f(1)*f(0)+f(0)*f(1)=2

f(3)=f(2)*f(0)+f(0)*f(2)+f(1)*f(1)=5

f(4)=f(3)*f(0)+f(0)*f(3)+f(2)*f(1)+f(1)*f(2)=5+5+2+2=14

f(5)=f(4)*f(0)+f(0)*f(4)+f(3)*f(1)+f(1)*f(3)+f(2)*f(2)=14+14+5+5+4=42

n=5,形态数量42,每种形态对应节点放置P(5,5)=5*4*3*2*1=120,总的可能120*42=5040,故n=30枚举该题,肯定超时。

基本确认,最终树的形态,关于根节点,接近对称。

大致算法如下:

将节点分数自小到大排序,根节点放最小值,第二层自左到右,放第二小,第三小,第三层,自右到左,放第四小,第五小,第四层,自左到右,放第六小,第七小,依次类推。但感觉不好操作。

再想想。

首先,我们要做的就是设计状态,其实就是设计dp数组的含义,它要满足无后效性。
关注这个 左子树*右子树+根 我只要知道左子树分数和右子树分数和根的分数(已给出),不就可以了吗?管他子树长什么样!
所以,我们ff数组存的就是最大分数,怎么存呢?
我们发现:子树是一个或多个节点的集合。
那么我们可不可以开一个f[i][j]来表示节点i到节点j成树的最大加分呢?可以先保留这个想法(毕竟暂时也想不到更好的了)。

如果这样话,我们就来设计状态转移方程。
按照刚刚的设计来说的话,我们的答案就是f[1][n]了,那么我们可以从小的子树开始,也就是len,区间长度。有了区间长度我们就要枚举区间起点,i为区间起点,然后就可以算出区间终点j。很明显,区间动规。
通过加分二叉树的式子我们可以知道,二叉树的分取决于谁是根,于是我们就在区间内枚举根k。
特别的,f[i][i]=a[i]其中a[i]为第i个节点的分数。
因为是要求最大值,所以我们就可以设计出

f[i][j]=MAX(f[i][k-1]*f[k+1][j]+f[k][k])

请注意:f[i][k-1]左子树,k根,f[k+1][j]右子树,与题中给定的中序遍历完全对应

于是乎,我们就自己设计出了一个dp过程,因为是顺着来的,所以很少有不成立的。

至于输出前序遍历,我们再设计一个状态root[i][j]来表示节点i到节点j成树的最大加分所选的根节点。
所以我们按照根->左->右的顺序递归输出即可。

样例最大值过程模拟如下:

f[1][1]=5,f[2][2]=7,f[3][3]=1,f[4][4]=2,f[5][5]=10
f[1][0]=1,f[2][1]=1,f[3][2]=1,f[4][3]=1,f[5][4]=1

len=1
以节点1为根节点f[1][2]=max(f[1][2],f[1][0]*f[2][2]+f[1][1])=12
以节点2为根节点f[1][2]=max(f[1][2],f[1][1]*f[3][2]+f[2][2])=12


以节点2为根节点f[2][3]=max(f[2][3],f[2][1]*f[3][3]+f[2][2])=8
以节点3为根节点f[2][3]=max(f[2][3],f[1][1]*f[3][2]+f[2][2])=12

......

ybt

通过

测试点结果内存时间
测试点1答案正确620KB2MS
测试点2答案正确632KB2MS
测试点3答案正确628KB1MS
测试点4答案正确616KB1MS
测试点5答案正确640KB1MS

LOJ


LUOGU

 

 AC代码如下:

#include <bits/stdc++.h>
#define LL long long
#define maxn 35
using namespace std;
LL f[maxn][maxn];
int root[maxn][maxn];
void PreOrder(int lt,int rt){
	if(lt>rt)return;
	printf("%d ",root[lt][rt]);
	PreOrder(lt,root[lt][rt]-1);
	PreOrder(root[lt][rt]+1,rt);
}
int main(){
	int n,lt,rt,len,k,i;
	scanf("%d",&n);
	for(i=1;i<=n;i++)scanf("%lld",&f[i][i]);
	for(i=1;i<=n+1;i++)f[i][i-1]=1;//空子树 
	for(i=1;i<=n;i++)root[i][i]=i;
	for(len=1;len<n;len++)
		for(lt=1;lt<n;lt++){
			rt=lt+len;
			if(rt>n)break;
			for(k=lt;k<=rt;k++)//以k为根节点 
				if(f[lt][rt]<f[lt][k-1]*f[k+1][rt]+f[k][k]){
					f[lt][rt]=f[lt][k-1]*f[k+1][rt]+f[k][k];
					root[lt][rt]=k;
				}
		}
	printf("%lld\n",f[1][n]);
	PreOrder(1,n);
	printf("\n");		
	return 0;
} 

该题翻看了思路,但代码却是独立编写。

该题习得什么?

与中序遍历匹配的树上区间动规

后续:

若题干,一开始给的是前序遍历,或后序遍历,那怎么处理?

其实,只要状态转移方程作变更,就可以了,本质还是区间动态规划。

题干中若是给 前序遍历f[i][j]=MAX(f[i+1][k]*f[k+1][j]+f[i][i])

题干中若是给 后序遍历f[i][j]=MAX(f[i][k]*f[k+1][j]+f[j][j])

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值