noip 2003 加分二叉树(DP)

题目链接

思路:

用一个二维数组dp[i, j]表示中序遍历中从 i 到 j 的区间组成的子树的集合,dp[i, j]的值表示这些子树中得分的最大值,转移方程很简单,dp[i, j] = max(dp[i, j], dp[i, k - 1] + dp[k + 1, r],需要遍历 i 到 j 的每一个点,判断哪一个是根节点,然后根据根节点把树再次分为左右子树

注意计算顺序,为了保证计算当前值的时候需要用到的值已经被计算过了,需要对区间的长度进行遍历,区间长度从短到长计算,其次遍历起点位置,这样可以保证在计算dp[i, j]时所需要的dp[i, k - 1]出现过(显而易见,k - 1 在 j 左侧),也保证dp[k + 1, j]出现过(也很显然,因为长度小于当前长度),对于输出前序遍历,只需要在每次更新的时候记录当前的根节点是哪一个点即可,输出的时候先输出整个区间的根,然后依次往左往右递归即可

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
int n;
ll points[33], dp[33][33], root[33][33];
void out(int l, int r){
	if(l > r)return;
	int k = root[l][r];
	printf("%d ", k);
	out(l, k - 1);
	out(k + 1, r);
}
int main()
{
	scanf("%d", &n);
	for (int i = 1; i <= n; i++){
		scanf("%lld", &points[i]);
	}
	
	for (int len = 1; len <= n; len++){
		for (int l = 1; l + len - 1 <= n; l++){
			int r = l + len - 1;	
			for (int k = l; k <= r; k++){
				ll lef, rig, s;
				if(l == k){
					lef = 1;
				}
				else lef = dp[l][k - 1];
				if(k == r)rig = 1;
				else rig = dp[k + 1][r];
				s = rig * lef + points[k];
				if(l == r)s = points[k];
				if(dp[l][r] < s){
					dp[l][r] = s;
					root[l][r] = k;
				}
			}
		}
	}
	printf("%d\n", dp[1][n]);
	out(1, n);
	return 0;
}

 

posted @ 2019-08-19 23:19  correct  阅读(76)  评论(0)    收藏  举报