[题解] 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;
}