[题解] P1040 加分二叉树 区间DP

[题解] P1040 加分二叉树

  这是本人第一次写题解,如有不足欢迎提出~

    洛谷题目链接

    牛客题目链接

    这个题可以用区间DP来解决,因为加分计算为:subtree 的左子树的加分 *subtree 的右子树的加分+subtree 的根的分数,设f[i][j]表示区间[ij]代表子树的最大分值,可以写出以下转移方程

        f[i][j]=max(f[i[k-1]*f[k+1][j]+f[k][k]) (i<=k<=j)

代码如下:

for(int k=l+1;k<r;k++)
    f[l][r]=max(f[i][j],dp(i,k-1)*dp(k+1,j)+dp(k,k))

    当l=r时返回l的分值,l+1=r时返回l,r的分值的和, 最后答案就是dp(1,n), 这样显然会TLE,所以在搜索过程中记录f[i][j]d值,时间复杂度为O(n^3) 可以解决本题

    注意到还需要输出分值最大时的前序遍历,就在计算f[i][j]时记录[i,j]这棵子树分值最大时的根节点编号,这里用fa[i][j]来储存,最终dp的函数代码如下

long long dp(int l,int r){
    if(f[l][r])return f[l][r];
    if(l==r)return a[l];
    if(l==r-1)return a[l]+a[r];

    long long ans=0;
    for(int k=l+1;k<r;k++){
        long long _ans=dp(l,k-1)*dp(k+1,r)+dp(k,k);
        if(_ans>ans)ans=_ans,fa[l][r]=k;
    }
    f[l][r]=ans;
    return ans;
}

    最后考虑输出结果,输出前序遍历时先输出根节点编号,然后再分别输出两棵子树的前序遍历,当l=r或l+1=r时就直接输出节点,输出前序遍历的代码如下

void print_ans(int l,int r){
    if(l<=r){
        if(l==r)printf("%d ",l);
        else if(l==r-1)printf("%d %d ",l,r);
        else{
            int k=fa[l][r];
            printf("%d ",k);
            print_ans(l,k-1);print_ans(k+1,r);
        }
    }
}

最后注意数据范围,记得开long long

AC代码:

#include <cstdio>
#include <algorithm>

using namespace std;

const int maxn=30+7;
int n,fa[maxn][maxn];
long long f[maxn][maxn],a[maxn];

long long dp(int l,int r){
    if(f[l][r])return f[l][r];
    if(l==r)return a[l];
    if(l==r-1)return a[l]+a[r];

    long long ans=0;
    for(int k=l+1;k<r;k++){
        long long _ans=dp(l,k-1)*dp(k+1,r)+a[k];
        if(_ans>ans)ans=_ans,fa[l][r]=k;
    }
    f[l][r]=ans;
    return ans;
}

void print_ans(int l,int r){
    if(l<=r){
        if(l==r)printf("%d ",l);
        else if(l==r-1)printf("%d %d ",l,r);
        else{
            int k=fa[l][r];
            printf("%d ",k);//先输出子树的根节点
            print_ans(l,k-1);print_ans(k+1,r);//再输出左右两棵子树
        }
    }
}

int main(){
    scanf("%d",&n);for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    printf("%d\n",dp(1,n));
    print_ans(1,n);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值