#include using namespace std; const int N = 50; int n; int w[N]; // 权值数组 int f[N][N]; // DP数组 : i,j区间内的最大得分 int g[N][N]; // 路径数组:i,j区间内的最大得分,是在k这个节点为根的情况下获得的 // 前序遍历输出路径 void out(int l, int r) { /* 其实,最后一个可以执行的条件是l=r,也就是区间内只有一个节点 当只有一个节点时,g[l][r] = l = r ①,此时继续按划分逻辑划分的话: 就是: [l,g[l][r]-1],[g[l][r]+1,r] ② 将①代入②,就是[l,l-1],[r+1,r],很明显,当出现前面比后面还大时,递归应该结束了 */ if (l > r) return; cout << g[l][r] << " "; // 输出根结点,前序遍历 out(l, g[l][r] - 1); // 递归左子树 out(g[l][r] + 1, r); // 递归右子树 } int main() { scanf("%d", &n); for (int i = 1; i <= n; i++) scanf("%d", &w[i]); /* DP初始化 ① 因为左、右子树为空时,返回1,这其实也是为计算公式准备前提,按返回1计算时,无左右子树的点 ,也就是叶子节点时,得分就是w[i] ② 此时,记录的辅助路径数组,g[i][i]表示的就是从i到i的区间内,根是谁,当然是i Q:为什么一定要记录g[i][i]=i,不记录不行吗?一个点的区间为什么也要记录根是谁呢? 答:因为这是递归的出口,如果不记录的话,那么out输出时就会找不到出口,导致死循环,TLE或者MLE */ for (int i = 1; i <= n; i++) f[i][i] = w[i], g[i][i] = i; // 区间DP for (int len = 2; len <= n; len++) // 单个节点组成的区间都是初值搞定了,我们只讨论两个长度的区间 for (int l = 1; l + len - 1 <= n; l++) { // 枚举左端点 int r = l + len - 1; // 计算右端点 // 枚举根节点k, 两个区间:[l,k-1],[k+1,r],根节点k也占了一个位置 // 注意:k是可以取到r的,原因论述见题解 for (int k = l; k <= r; k++) { // 根据题意特判 int ls = k == l ? 1 : f[l][k - 1]; // 左子树为空,返回1 int rs = k == r ? 1 : f[k + 1][r]; // 右子树为空,返回1 // 得分计算公式 int t = ls * rs + w[k]; // 记录取得最大值时的根节点k if (f[l][r] < t) { f[l][r] = t; g[l][r] = k; // 记录l~r区间的最大得分是由哪个根节点k转化而来 } } } // 输出 cout << f[1][n] << endl; // 利用递归,输出字典序路径 out(1, n); return 0; }