P1040 加分二叉树

区间dp 或 树形dp的小题目

这道题我刚学OI的时候就看过,结果以为要暴力建出所有形态的树,其实还是太naive了啊!

因为给你的是中序遍历,而且还是有序的\(1~n\),所以你可以随便枚举一个点做当前子树的根,那么左边的中序就是左子树了,右边同理。

这样写可以很方便地写一个记忆化搜索(不会递推),随便写一写就能够得到分数。


然后问题来了:如何求出这棵树的形态?

解决方法:再开一个数组,表示在当前区间里面是取哪个点为根是最优的。那么我们就可以沿着这个标记走出前序遍历了。

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
const int maxn = 35;
const int INF = 0x3f3f3f3f;
int w[maxn];
int dp[maxn][maxn];
int n;
int root[maxn][maxn];
int solve(int l, int r, int fa, bool right)
{
    if(l > r) return 1;
    if(dp[l][r] != -INF) return dp[l][r];
    for(int i = l; i <= r; i++)
    {
        if(dp[l][r] < solve(l, i - 1, i, 0) * solve(i + 1, r, i, 1) + w[i])
        {
            dp[l][r] = solve(l, i - 1, i, 0) * solve(i + 1, r, i, 1) + w[i];
            root[l][r] = i;
        }
    }
    return dp[l][r];
}
void preorder(int l, int r)
{
    if(l > r) return;
    printf("%d ", root[l][r]);
    preorder(l, root[l][r] - 1);
    preorder(root[l][r] + 1, r);
}
int main()
{
    scanf("%d", &n);
    for(register int i = 0; i <= n; i++) for(register int j = 0; j <= n; j++) dp[i][j] = -INF;
    for(int i = 1; i <= n; i++)
    {
        scanf("%d",&w[i]);
        dp[i][i] = w[i];
        root[i][i] = i;
    }
    printf("%d\n", solve(1, n, 0, 1));
    preorder(1, n);
    printf("\n");
    return 0;
}
posted @ 2018-10-31 13:39  Garen-Wang  阅读(96)  评论(0编辑  收藏  举报