一道比较经典的区间dp题目
首先我们设dp[i][j]表示我们用i-j这些点构造树的最大收益是多少
首先dp[i][i]=a[i](只有一个点,最大价值即点本身价值)
现在,我们考虑转移:
因为一颗子树的价值=左子树价值*右子树价值+根价值
那么,如果我们知道了根,左子树,右子树后,就可以进行转移了
于是,我们先枚举根,但是,如果再枚举哪些点放左子树,哪些放右子树的话,明显复杂度就爆炸了,怎么办呢?
注意到,题目有个条件:树的中序遍历为1...n,那么,就有一个小性质:
i的左子树都是编号比i小的点,i的右子树都是编号比i大的点。
于是,如果我们枚举了根的话,假设为k,那么就有:
左子树组成的点:l...k-1
右子树组成的点:k+1...r
注意到,这是两个连续的区间,那么转移过来直接使用dp[l][k-1]和dp[k+1][r]的值即可
于是有转移:
但是需要注意的是,我们枚举根的过程中,如果k==i||k==j,如果我们不做处理的话,会使得dp[i][k-1]||dp[k+1][j]的值为0,导致转移错误,所以,为了正确的转移,我们需要在开始的时候,把所有的dp[i][i-1]的值赋为1
而,转移过程中,为了保证转移正确性,我们观察式子,i大的要先算所以我们倒着枚举i,j小的要后算,所以我们顺着枚举j,就行了。
至于输出前序遍历,我们可以直接对区间枚举根,然后看看是否是有这个枚举的根转移过来的,若是,当前区间的根即是这个,否则继续枚举就行了。
当然,你也可以在dp转移的时候,同时维护下每个区间的根
代码:
#include<bits/stdc++.h> using namespace std; #define int long long const int N=31; int a[N],dp[N][N],n; inline void dfs(int l,int r){ if(l>r){ return; } if(l==r){ printf("%d ",l); return; } for(int i=l;i<=r;++i){ if(dp[l][r]==dp[l][i-1]*dp[i+1][r]+a[i]){ printf("%d ",i); dfs(l,i-1),dfs(i+1,r); return; } } } signed main(){ scanf("%lld",&n); for(int i=1;i<=n;++i){ scanf("%lld",&a[i]); dp[i][i]=a[i];dp[i][i-1]=1; } dp[n+1][n]=1; for(int i=n;i;--i){ for(int j=i+1;j<=n;++j){ for(int k=i;k<=j;++k){ dp[i][j]=max(dp[i][j],dp[i][k-1]*dp[k+1][j]+a[k]); } } } printf("%d\n",dp[1][n]); dfs(1,n); return 0; }