题目链接:点击这里
题目大意:
构造一颗
n
n
n 个节点的二叉树,其编号为
1
,
2
,
3
,
…
,
n
1,2,3,…,n
1,2,3,…,n ,每个节点都有一个权值
w
i
w_i
wi
子树的权值
=
=
= 左子树权值
∗
*
∗ 右子树权值
+
+
+ 根节点权值
若某个子树为空,规定其权值为
1
1
1。叶子的加分就是叶节点本身的分数,不考虑它的空子树。
试求一棵符合中序遍历的节点编号为
1
,
2
,
3
,
…
,
n
1,2,3,…,n
1,2,3,…,n且加分最高的二叉树
输出其整棵树的最高分和此分数下字典序最小的前序遍历
题目分析:
因为此二叉树的中序遍历为
1
,
2
,
3
,
.
.
.
,
n
1,2,3,...,n
1,2,3,...,n ,当我们确定根节点编号为
k
k
k 时,其左子树的节点编号一定为
1
1
1 到
k
−
1
k-1
k−1 ,右子树的节点编号一定为
k
k
k 到
n
n
n ,是互不影响的,我们考虑区间
d
p
dp
dp
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示编号为
i
i
i 到
j
j
j 这一段构成的子树的最大权值
容易有转移方程:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
]
[
k
−
1
]
∗
d
p
[
k
+
1
]
[
j
]
+
w
[
k
]
)
dp[i][j]=max(dp[i][k-1]*dp[k+1][j]+w[k])
dp[i][j]=max(dp[i][k−1]∗dp[k+1][j]+w[k])
而此题还要求方案,且字典序最小,那么一定要有先转移
k
k
k 较小的情况,所以我们只需要边转移边记录,最后递归输出方案即可
具体细节见代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
//#define int ll
using namespace std;
int read()
{
int res = 0,flag = 1;
char ch = getchar();
while(ch<'0' || ch>'9')
{
if(ch == '-') flag = -1;
ch = getchar();
}
while(ch>='0' && ch<='9')
{
res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
ch = getchar();
}
return res*flag;
}
const int maxn = 105;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
struct node{
int x,y;
}nod[maxn*maxn];
int n,w[maxn],dp[maxn][maxn],root[maxn][maxn];//root[i][j]表示dp[i][j]从哪里转移过来的
void dfs(int l,int r)
{
if(l > r) return ;
int mid = root[l][r];
printf("%d ",mid);
dfs(l,mid-1);dfs(mid+1,r);
}
signed main()
{
n = read();
for(int i = 1;i <= n;i++) w[i] = read();
for(int len = 1;len <= n;len++)
for(int i = 1;i+len-1 <= n;i++)
{
int j = i+len-1;
if(len == 1) dp[i][j] = w[i],root[i][j] = i;//特判叶子节点
else {
for(int k = i;k <= j;k++)
{
int l = k==i ? 1 : dp[i][k-1];
int r = k==j ? 1 : dp[k+1][j];
int tmp = l*r+w[k];
if(tmp > dp[i][j]) dp[i][j] = tmp,root[i][j] = k;
}
}
}
printf("%d\n",dp[1][n]);
dfs(1,n);
return 0;
}