划重点:二叉树必用二分递归的算法。
此题目初学者理解比较困难,需要有dfs算法基础,且熟悉二叉树算法。
解题思路:拿到问题之后,先用暴力枚举的思路去思考解法。首先任何一个点都可能是树根,
假设我们选择第i个点作为树根,那么树结构就是左子树[1,i-1] ,树根i,右子树 [i+1,n]。
在[1,i-1]中要选择一个点作为左子树树根(假设是j),在[i+1,n]中选一个点作为右子树的树根(假设是k),
那么结果就是:左子树(根为j时)的最优值+a[j]*a[i]+右子树(根为k)最优值+a[k]*a[i]
分析到这里能得到,一棵树的最优值不但和其自身有关,还和这棵树的父节点有关。比如左子树根j的选择,不能只考虑“左子树自身的最优值”,必须综合考虑树根j和树根父节点i的乘积。
因此设计递归函数时,需要三个参数dfs(int l,int r,int root),[l,r]中序序列区间,root是[l,r]对应二叉树的父节点,在[l,r]之间选择树根j时,最终计算结果必须得考虑j和root的乘积。
我们把整个序列[1,n]的父节点定义为0号结点,其a[0]=0。(这是树结构中一种常用的边界处理技术,给树根一个不存在或值为0的父节点,便于计算)。
为了避免重复计算,此处使用了记忆化搜索。(其实本问题和区间dp思想是类似的,不过一提到二叉树自然而然就会想到递归下降的方法)
#include <bits/stdc++.h>//记忆化dfs
typedef long long ll;
using namespace std;
ll n,a[305],dp[305][305][305]; /**<dp[l][r][root]表示l...r是一颗子树,树根不确定,但这颗子树的父节点是root */
ll dfs(int l,int r,int root) /**< 其实root不是l-1,就是r+1,思考中序序列中树根的位置 */
{
if(l>r)
return 0;
if(dp[l][r][root]<=3e9)
return dp[l][r][root];
ll i,j,k,ans=3e9;
for(i=l; i<=r; i++) /**< i为[l,r]根时,左右子树进行划分 */
{
int temp1=dfs(l,i-1,i);
int temp2=dfs(i+1,r,i);
ans=min(ans,temp1+temp2+a[i]*a[root]);/**< 选i为根是否合适还得考虑上a[i]*a[root] */
}
return dp[l][r][root]=ans;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0);
int i,j,k,l,ans=INT_MAX;
memset(dp,127/3,sizeof(dp));/**< dp数组初始化 */
cin>>n;
for(i=1; i<=n; i++)
cin>>a[i];
dfs(1,n,0);/**< 1到n的树的最优值,0是不存在的树根 */
cout<<dp[1][n][0];
return 0;
}