加分二叉树

以下内容来自洛谷:http://www.luogu.org/problem/show?pid=1040
设一个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的前序遍历
输入:
第1行:一个整数n(n<30),为节点个数。
第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
输出:
第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。
第2行:n个用空格隔开的整数,为该树的前序遍历。
样例输入:
5
5 7 1 2 10
样例输出:
145
3 1 2 4 5
以上内容来自洛谷:http://www.luogu.org/problem/show?pid=1040

=================================================================================

状态转移方程:
f(k,k)=value[k]【1<=k<=n】
f(i,j)=max{f(i,i)+f(i+1,j) , f(j,j)+f(i,j-1) , f(k,k)+f(i,k-1)*f(k+1,j)  } 【i<k<j】
f(i,j)表示以i为中序遍历的最左边,j为中序遍历的最右边所组成的一棵树的最大积分。
第一行不用解释了,就是如果这棵树只包含根节点(也就是这是叶子),那么他的积分等于自己的价值。

第二行的max部分包括3大部分:

第一个部分,是以最左端为根节点,剩下的部分都是右子树的情况。那么他的分数就是根节点的分数+右子树的分数(因为左子树为空)
那么f(i,i)就是根节点的分数,f(i+1,j)就是右子树的分数
第二个部分,是以最右端为根节点,剩下的部分都是左子树的情况。那么他的分数就是根节点的分数+左子树的分数(因为右子树为空)
那么f(j,j)就是根节点的分数,f(i,j-1)就是右子树的分数
第三个部分是以节点k为根节点的情况。节点k的取值范围是i和j之间,但是不包括i和j(因为这两种情况前面算过了)。
那么分数就是根节点的分数+左子树的分数*右子树的分数。f(k,k)就是根节点的分数,f(i,k-1)是左子树的分数,f(k+1,j)是右子树的分数。

那么有人问了,为甚么要把前两种情况分开写,而不都算作第三种情况呢?我说:如果这么算的话,f(i,j)应该初始化为1【i>j】,你想这么写就自己打循环初始化dp数组吧

打印前序遍历:

还有一个问题,如何打印一棵树的前序遍历呢?我们想一想,打印一棵树的某种遍历,都要找到一棵树的根节点,左子树,右子树。由于题目输入的是中序遍历,给出根节点的位置和左右边界就可以打印这棵树的前序遍历了。我们用一个root[i][j]的二维数组存储来存储中序遍历从i到j的一棵树的根节点的位置。这个数组的元素和dp数组一起更新。至于打印这棵树树,只是一个递归的事情了。

代码:

在dp的时候,注意要先按照中序遍历的长度搜,然后按照头结点搜,这样才是正确的(自己可以想下为什么)
下面是Cpp代码,注释懒得写(注意dp数组就是f数组,dfs函数其实就是一个dp,这些名字我用惯了也懒得改了)

#include <iostream>
#include <cstring>
using namespace std;
int root[32][32],dp[32][32],n,a[32];
int getmax(int i,int j)//计算dp[i][j] and root[i][j]
{
    int ans=max(dp[i][i]+dp[i+1][j],dp[j][j]+dp[i][j-1]);
    root[i][j]=(ans==dp[i][i]+dp[i+1][j])?i:j;
    for(int x=i+1;x<=j-1;x++)
        if(ans<dp[x][x]+dp[i][x-1]*dp[x+1][j])
        {
            ans=dp[x][x]+dp[i][x-1]*dp[x+1][j];
            root[i][j]=x;
        }
    return ans;
}
int printTree(int l,int r)//打印 中序遍历左端为l 右端为r的树
{
    if(l<=r)
    {
        cout << root[l][r] << ' ';
        printTree(l,root[l][r]-1);
        printTree(root[l][r]+1,r);
    }
}
void dfs()//动态规划过程,dfs个毛啊
{
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
        dp[i][i]=a[i];
        root[i][i]=i;
    }
    for(int d=1;d<n;d++)//d是长度
        for(int i=1;i<=n-d;i++)
            dp[i][i+d]=getmax(i,i+d);
}
int main()
{
    cin >> n;
    for(int i=1;i<=n;i++)
        cin >> a[i];
    dfs();
    cout << dp[1][n] << endl;
    printTree(1,n);
    cout << endl;
    return 0;
}

 

posted @ 2016-08-30 21:06  ghj1222  阅读(395)  评论(0编辑  收藏  举报