|
|
#include <bits/stdc++.h>
|
|
|
|
|
|
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;
|
|
|
}
|