P1040 加分二叉树(区间DP)

(点击此处查看原题)

解题思路

题目已经给出了树的中序遍历,因此我的想法是利用中序遍历的特点:若某子树的根结点为k,那么k之前的结点组成这一子树的左子树,k之后的结点组成这一子树的右子树,可以通过不断地枚举每个子树的根结点k,求出每个子树的最大加分:{ 左子树的最大加分*右子树的最大加分+ 根结点k的值}

以上是通过已知中序遍历想到是方法,结合已知条件,对于某一子树的中序遍历: {l, l + 1, ... , r} ,若根节点为k,那么 {l, l +1,...,k-1} 即为这一子树的左子树,{k+1,k+2,...,r}即为这一子树的右子树,因此,可以通过这种方法构造所有可能的树结构

根据上面的方法,我们通过递归求出每一段中序遍历{l,l+1,...,r}代表的子树的最大加分dp[l][r]以及根结点root[l][r],根据状态转移方程

dp[l][r] = max(dp[l][r],dp[l][k-1] + dp[k+1][r] + val[k]) { l <= k <= r}

并记录dp[l][r]取最大值时的根结点root[l][r],这样一来dp[1][n]即为我们所求的最大加分,又利用root和中序遍历求出前序遍历

代码区

#include
#include
#include
#include
#include
#include
#include<string>
#include
#include
#include
#include 
#include 

#define bug cout << "**********" << endl
#define show(x, y) cout<<"["<#define LOCAL = 1;
using namespace std;
typedef long long ll;
const ll inf = 1e18 + 7;
const int mod = 1e9 + 7;
const int Max = 1e5 + 10;

int n;
ll val[31];
ll dp[31][31];        //表示[l,r]子树的最大加分
int root[31][31];    //表示[l,r]子树的根结点
//dp,root均为以[l,r]组成的子树的数据

ll dfs(int l, int r)
{
    if (l > r)                        //空树
        return 1;
    if(l == r)                        //叶子节点
        return dp[l][r] = val[l];

    if (dp[l][r] != -1)
    {
        return dp[l][r];
    }

    for (int k = l; k <= r; k++)    //枚举子树[l,r]的根结点
    {
        ll now = dfs(l,k-1) * dfs(k + 1, r) + val[k];
        if(now > dp[l][r])
            dp[l][r] = now,root[l][r] = k;
    }
    return dp[l][r];
}

void dfs2(int l,int r)
{
    if(l > r)                        //空树
        return;
    printf("%d ",root[l][r]);
    dfs2(l,root[l][r] - 1);
    dfs2(root[l][r] + 1, r);
}

int main()
{
#ifdef LOCAL
    //    freopen("input.txt", "r", stdin);
    //    freopen("output.txt", "w", stdout);
#endif
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%lld", val + i),root[i][i] = i;

    memset(dp,-1,sizeof(dp));
    dfs(1,n);

    printf("%lld\n",dp[1][n]);
    dfs2(1,n);
    printf("\n");
    return 0;
}
View Code

你可能感兴趣的:(P1040 加分二叉树(区间DP))