问题描述:在线性代数里,我们都学过矩阵的乘法。矩阵的乘法不满足分配律,但是满足结合律,因此 (A x B)x C 与 A x(B x C)的运算结果是一样的,但是中间的运算量却可能不一样。
例如:假设A、B、C矩阵分别是 2 x 3、3 x 4、4 x 5 的,则 (A x B)x C 的运算量是 2 x 3 x 4 + 2 x 4 x 5 = 64,但是 A x(B x C)的运算量是 3 x 4 x 5 + 2 x 3 x 5 = 90。(想想运算量为什么是这么计算的。)显然第一种计算方法比较节省计算量。
好,现在考虑下面的问题:给出n个矩阵组成的序列,设计一种方法,把他们按照一定的顺序依次乘起来(你也可以理解为不断地加括号),是的总的计算量尽量小。假设第i个矩阵 Ai 是pi-1 X pi的。
解题思路:用dp[i][j]表示从i矩阵到j矩阵的最大链乘,然后考虑进行状态转移,因为链乘矩阵满足结合律,可以分解成i到k的链乘与k+1到j的链乘加上两组合矩阵链乘。即dp[i][j]=min{dp[i][k]+dp[k+1][j]+A[i].a*A[k].b*A[j].b}。利用记忆搜索法递归很容易能够求得最优解:
memset(d,-1,sizeof(d));
for(int i=1;i<=n;i++)
{
d[i][i]=0;
}
int f(int i,int j)
{
int &a=d[i][j];
if(a!=-1) return a;
a=inf;
for(int k=i;k<j;k++)
{
a=min(a,f(i,k)+f(k+1,j)+A[i].a*A[k].b*A[j].b);
}
return a;
}
然而使用递推的解法则递推顺序需要考虑,如果直接定义i,j,k递增或递减枚举其实并不正确,因为这么一来相当于是推以i为起点,终点不断枚举,再枚举中间段,可是中间段此时未知。正确的递推方法是以遍历矩阵链乘的长度,定义len作为最外层循环,从2循环至n:
for(int i=1;i<=n;i++) //初始化
{
for(int j=1;j<=n;j++)
{
dp[i][j]=inf;
}
}
for(int i=1;i<=n;i++) //边缘初始化
{
dp[i][i]=0;
}
for(int len=2;len<=n;len++) //以链乘长度作为最外层枚举
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
for(int k=i;k<j;k++)
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+A[i].a*A[k].b*A[j].b);
}
}
}