题目连接:https://www.luogu.com.cn/problem/P1040 博客园食用连接:https://www.cnblogs.com/lonely-wind-/p/13854421.html
设一个 n 个节点的二叉树 tree \text{tree} tree的中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,…,n),其中数字 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第 i i i个节点的分数为 d i d_i di, tree \text{tree} tree及它的每个子树都有一个加分,任一棵子树 subtree \text{subtree} subtree(也包含 tree \text{tree} tree本身)的加分计算方法如下:
subtree \text{subtree} subtree的左子树的加分 × subtree \times \text{subtree} ×subtree 的右子树的加分 + subtree \text{subtree} subtree的根的分数。
若某个子树为空,规定其加分为 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,…,n)且加分最高的二叉树 tree \text{tree} tree。要求输出
tree \text{tree} tree的最高加分。
tree \text{tree} tree的前序遍历。
第 1 行 1 个整数 n,为节点个数。
第 2 行 n 个用空格隔开的整数,为每个节点的分数
第 1 行 1 个整数,为最高加分 ( A n s ≤ 4 , 000 , 000 , 000 ) (Ans \le 4,000,000,000) (Ans≤4,000,000,000)。
第 2 行 n 个用空格隔开的整数,为该树的前序遍历。
输入 5 5 7 1 2 10 输出 145 3 1 2 4 5
说明/提示 n< 30 分数 <100
emmm,如果知道这是个区间DP的话,那么久挺好做的,我们只需要对每个区间枚举其根放的最优位置就好了,那么久很容易得到方程: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i ] [ k − 1 ] ∗ d p [ k + 1 ] [ j ] + p o t [ k ] ) dp[i][j]=max(dp[i][j],dp[i][k-1]*dp[k+1][j]+pot[k]) dp[i][j]=max(dp[i][j],dp[i][k−1]∗dp[k+1][j]+pot[k]) 其中k为其根的位置,也就是区间DP分割的位置。实际上当我们看到上面那个计算公式的时候也应该反应过来是个区间DP,它以根为分割点,然后左右合并的。 那么最后的答案也就出来了,就是 d p [ 1 ] [ n ] dp[1][n] dp[1][n]
当然,这题如果就这样的话那就很简单了,接下来比较难操作的就是记录其前序遍历,前序遍历是根左右,那么我们上面不是已经确定了每个区间的根了吗,那么我们将每个区间的根保留下来最后来个递归输出就完事了。
那么可以得到理论AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=100; ll pot[mac],dp[mac][mac]; int root[mac][mac]; void print_pre(int l,int r) { if (!root[l][r]) return; printf ("%d ",root[l][r]); if (l>=r) return; print_pre(l,root[l][r]-1); print_pre(root[l][r]+1,r); } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%lld",&pot[i]); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) dp[i][j]=1; for (int i=1; i<=n; i++) dp[i][i]=pot[i],root[i][i]=i; for (int len=2; len<=n; len++){ for (int i=1; i+len-1<=n; i++){ int j=i+len-1,rt=0; for (int k=i; k<=j; k++){ if (dp[i][k-1]*dp[k+1][j]+pot[k]>dp[i][j]) rt=k,dp[i][j]=dp[i][k-1]*dp[k+1][j]+pot[k]; } root[i][j]=rt; } } printf ("%lld\n",dp[1][n]); print_pre(1,n); return 0; }但实际上我们可以发现。。。。。这题它没有SPJ,那么我们只能看着他的样例来一手盲猜其隐含条件是:输出字典序最小的先序遍历。
然后我们只需要在转移条件相等的时候取一个最小的根就可以了。。。。然后发现还是没有变化,于是我们似乎发现了点小问题, d p [ i ] [ k − 1 ] dp[i][k-1] dp[i][k−1]它越界了!!! k − 1 k-1 k−1会跑到0去,但我们的dp的关于0这一层并没有初始化,所以。。。。。初始化之后也就不需要什么花里胡哨的了,就可以直接AC了。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int mac=100; ll pot[mac],dp[mac][mac]; int root[mac][mac]; void print_pre(int l,int r) { if (!root[l][r]) return; printf ("%d ",root[l][r]); if (l>=r) return; print_pre(l,root[l][r]-1); print_pre(root[l][r]+1,r); } int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); for (int i=1; i<=n; i++) scanf ("%lld",&pot[i]); for (int i=0; i<=n; i++) for (int j=0; j<=n; j++) dp[i][j]=1; for (int i=1; i<=n; i++) dp[i][i]=pot[i],root[i][i]=i; for (int len=2; len<=n; len++){ for (int i=1; i+len-1<=n; i++){ int j=i+len-1,rt=99999; for (int k=i; k<=j; k++){ if (dp[i][k-1]*dp[k+1][j]+pot[k]>dp[i][j]) rt=k,dp[i][j]=dp[i][k-1]*dp[k+1][j]+pot[k]; } root[i][j]=rt; } } printf ("%lld\n",dp[1][n]); print_pre(1,n); return 0; }