洛谷 P1040 加分二叉树(树形DP,树的遍历)

6 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

已知树的中序遍历,而且知道树中的节点的分值,问怎么先序遍历这棵树使得分值最大,分值计算方法:

本节点分值=左子树分值*右子树分值+本节点分值。

空子树分值规定为1.

解题思路:

这题给的数据范围是30,很自然地我们可以使用暴力拆,以区间[l,r]作为dfs(深度优先遍历)的参数,每次枚举[l,r]中一点作为根,然后计算分值,但是注意边界情况,分别是空子树, l==r 还有 r==l+1时候的处理。 作为 一个 然后作为一名蒟蒻表示暴力拆的复杂度算不出来QAQ,后来瞄了一眼题解,发现是用记忆化DP做,所以我们通过[l,r]区间作为一个状态,遍历过这里能得到的最高分值了就不用再遍历了。

另外还有一个点是,先序遍历打印,因为在这里已知到中序,所以,我们每次只要知道这个区间中的根节点就能打印先序了。

废话:以本人有限的经验来看,表示树一般都是用一个数组,然后用数组的两个指针来 表示一棵树的起始位置。另外,这种树的遍历可以用区间加根的位置,然后得到树的左右区间来递归遍历咯。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=40;
int sumarr[MAXN];
int rootval[MAXN];
int n;
pair<int,int> memo[MAXN][MAXN];
vector<int> mvans;
int dfs(int l,int r){
	if(r==l)return rootval[l];
	if(r==l+1){
	return rootval[l]+rootval[r];
	}
	if(memo[l][r].first!=-1)return memo[l][r].first;	//DP
	int maxval=0;
	for(int root=l;root<=r;root++){
		int lb,rb;int lv,rv;
		if(root==l)lv=1;
		else {lb=root-1;
		lv=dfs(l,lb);
		}
		if(root==r){
			rv=1;
		}
		else {rb=root+1;
		rv=dfs(rb,r);
		}
		if(lv*rv+rootval[root]>maxval){
			maxval=lv*rv+rootval[root];
			if(lb-l==1){
				memo[l][lb].first=rootval[l]+rootval[lb];
				memo[l][lb].second=l;
			}
			if(r-rb==1){
				memo[rb][r].first=rootval[rb]+rootval[r];
				memo[rb][r].second=rb;
			}
			memo[l][r].first=maxval;
			memo[l][r].second=root;
		}
	}
	return maxval;
}
void print_tree(int l,int r){
	if(r<0||l>n)return;
	if(r==l+1){cout<<memo[l][l].second<<" "<<memo[r][r].second<<" ";
	return ;
	}
	if(l==r){cout<<memo[l][r].second<<" ";
	return;
	}
	cout<<memo[l][r].second<<" ";
	print_tree(l,memo[l][r].second-1);
	print_tree(memo[l][r].second+1,r);
}
int32_t main(){
	cin>>n;
	memset(sumarr,0,sizeof(sumarr));
	for(int i=0;i<MAXN;i++)
		for(int j=0;j<MAXN;j++){
			if(i && j && i==j){
				memo[i][j].first=rootval[i];
				memo[i][j].second=i;
				continue;
			}
			memo[i][j].first=memo[i][j].second=-1;
		}
	for(int i=1;i<=n;i++){
		int t;cin>>t;
		rootval[i]=t;
		sumarr[i]=sumarr[i-1]+t;
	}
	int ans;
	ans=dfs(1,n);
	cout<<ans<<endl;
	print_tree(1,n);
	cout<<endl;
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值