加分二叉树
题目
P1040
注:SSLOJ不用输出前序遍历
解析
看到这题有树,猜想是一个树形DP,但是这其实是一道区间DP/jk
转移方程:
d
p
i
,
i
−
1
=
1
,
d
p
i
,
i
=
a
i
(
1
<
=
i
<
=
n
)
dp_{i,i-1}=1,dp_{i,i}=a_i(1<=i<=n)
dpi,i−1=1,dpi,i=ai(1<=i<=n)
d
p
i
,
i
+
j
=
m
a
x
(
d
p
i
,
k
−
1
∗
d
p
k
+
1
,
i
+
j
+
a
k
)
dp_{i,i+j}=max(dp_{i,k-1}*dp_{k+1,i+j}+a_k)
dpi,i+j=max(dpi,k−1∗dpk+1,i+j+ak)
(
1
<
=
j
<
=
n
,
1
<
=
i
<
=
n
−
j
,
i
<
=
k
<
=
i
+
j
)
(1<=j<=n,1<=i<=n-j,i<=k<=i+j)
(1<=j<=n,1<=i<=n−j,i<=k<=i+j)
显然这没有办法输出前序遍历,于是我们考虑保存根,修改转移方程如下:
r
o
o
t
i
,
i
=
a
i
,
d
p
i
,
i
−
1
=
1
,
d
p
i
,
i
=
a
i
(
1
<
=
i
<
=
n
)
root_{i,i}=a_i,dp_{i,i-1}=1,dp_{i,i}=a_i(1<=i<=n)
rooti,i=ai,dpi,i−1=1,dpi,i=ai(1<=i<=n)
d
p
i
,
i
+
j
=
m
a
x
(
d
p
i
,
k
−
1
∗
d
p
k
+
1
,
i
+
j
+
a
k
)
dp_{i,i+j}=max(dp_{i,k-1}*dp_{k+1,i+j}+a_k)
dpi,i+j=max(dpi,k−1∗dpk+1,i+j+ak)
r
o
o
t
i
,
i
+
j
=
k
(
d
p
i
,
i
+
j
=
d
p
i
,
k
−
1
∗
d
p
k
+
1
,
i
+
j
+
a
k
)
root_{i,i+j}=k(dp_{i,i+j}=dp_{i,k-1}*dp_{k+1,i+j}+a_k)
rooti,i+j=k(dpi,i+j=dpi,k−1∗dpk+1,i+j+ak)
(
1
<
=
j
<
=
n
,
1
<
=
i
<
=
n
−
j
,
i
<
=
k
<
=
i
+
j
)
(1<=j<=n,1<=i<=n-j,i<=k<=i+j)
(1<=j<=n,1<=i<=n−j,i<=k<=i+j)
最后递归输出即可
code(洛谷):
#include<cstdio>
#define int long long
using namespace std;
int dp[35][35],root[35][35],n,val[35];
inline void dfs(int x,int y)
{
if(root[x][y])printf("%lld ",root[x][y]);
if(root[x][root[x][y]-1])dfs(x,root[x][y]-1);
if(root[root[x][y]+1][y])dfs(root[x][y]+1,y);
}
signed main()
{
scanf("%lld",&n);
for(int i=1;i<=n;++i)scanf("%lld",&val[i]),dp[i][i]=val[i],dp[i][i-1]=1,root[i][i]=i;
for(int k=1;k<=n;++k)for(int i=1;i+k<=n;++i)for(int j=i;j<=i+k;++j)if(dp[i][i+k]<dp[i][j-1]*dp[j+1][i+k]+val[j])dp[i][i+k]=dp[i][j-1]*dp[j+1][i+k]+val[j],root[i][i+k]=j;
printf("%lld\n",dp[1][n]);
dfs(1,n);
return 0;
}