NOIP2013加分二叉树

题目

设一个n个节点的二叉树tree的中序遍历为(1,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第i个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数。
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历

分析

乍一看像是一个树形dp。但仔细观察,给出一个中序遍历序列,相当于对于每一个节点,确定了该节点的左右子树。所以subtree在序列中就是一段连续的区间[i,j],以不同的k∈[i,j]作为根节点获得的最高加分,即为subtree的最高加分。那么本题就出现了几个关键词:区间,节点,最高……这不是区间dp吗?
用f[i][j]表示以k为根节点的区间[i,j]的最大加分,根据题中的计算方法,可以得到转移方程:

  • f[i][j]=max(f[i][k-1]f[k+1][j]+a[k]);

如何输出前序遍历?考虑在最短路问题中输出最短路径的方法,用g[i][j]存储最大加分下[i,j]的根节点k,当f[i][j]可更新时,更新g[i][j]以保存最大结果,最后进行递归建树。
别忘记初始化,空树的加分为1,即f[i][i+1]=1,还有f[i][i]=a[k];

代码

#include
#include
using namespace std;
const int maxn=33;
int a[maxn-1],g[maxn][maxn];
long long f[maxn][maxn];
int n,num;
void build(int i,int j)
{
    if(i>j)     return ;
    if(i==j)
    {
        printf("%d ",i);
        return ;
    }
    int k=g[i][j];
    printf("%d ",k);
    build(i,k-1);
    build(k+1,j);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        f[i][i]=a[i];
        f[i][i-1]=1;
    }
    for(int l=n;l>=1;l--)
        for(int r=l+1;r<=n;r++)
            for(int k=l;k<=r;k++)
                if(f[l][k-1]*f[k+1][r]+f[k][k]>f[l][r])
                    f[l][r]=f[l][k-1]*f[k+1][r]+a[k],g[l][r]=k;
    printf("%d\n",f[1][n]);
    build(1,n);
    return 0;
}

你可能感兴趣的:(动态规划)