问题描述
对于给定的n个矩阵形成的矩阵链 M 1 , M 2 , M 3 , . . . , M n M_1,M_2,M_3,...,M_n M1,M2,M3,...,Mn,求计算乘积 M 1 M 2 M 3 , . . . M n M_1M_2M_3,...M_n M1M2M3,...Mn时最少次标量相乘的运算顺序,这类问题称为矩阵链乘法问题(Matrix Chain Multiplication Problem)。
请编写一个程序,当给定矩阵 M i M_i Mi的维数后,求出计算n个矩阵的乘积 M 1 , M 2 , M 3 , . . . , M n M_1,M_2,M_3,...,M_n M1,M2,M3,...,Mn时所需标量相乘运算的最少次数。
输入: 第1行输入矩阵数n。接下来n行输入矩阵
M
i
(
i
=
1...
n
)
M_i(i=1...n)
Mi(i=1...n)的维数r、c。其中r代表矩阵的行数,c代表矩阵的列数,r、c均为整数,用空格隔开。
输出: 输出最少次数,占1行。
限制:
1 ≤ n ≤ 100
1 ≤ r, c ≤ 100
输入示例
6
30 35
35 15
15 5
5 10
10 20
20 25
输出示例
15125
讲解
l ∗ m l*m l∗m的矩阵A与 m ∗ n m*n m∗n的矩阵B相乘后得到 l ∗ n l*n l∗n的矩阵C,C的各元素 c i j c_{ij} cij可由下面的式子得出。
我们不关心 c i j c_{ij} cij的具体值,只希望尽量减少计算过程中的乘法次数。
n个矩阵相乘时,
M
i
M_i
Mi为
p
i
−
1
p_{i-1}
pi−1行
p
i
p_i
pi列的矩阵,我们以下图所示的
(
M
1
M
2
.
.
.
M
6
)
(M_1M_2...M_6)
(M1M2...M6)为例分析。
这戏矩阵乘积有多种计算顺序。我们按习惯的从左到右的顺序计算时可以写作
(
(
(
(
(
M
1
M
2
)
M
3
)
M
4
)
M
5
)
M
6
)
(((((M_1M_2)M_3)M_4)M_5)M_6)
(((((M1M2)M3)M4)M5)M6),从右到左计算时可以写作
(
M
1
(
M
2
(
M
3
(
M
4
(
M
5
M
6
)
)
)
)
)
(M_1(M_2(M_3(M_4(M_5M_6)))))
(M1(M2(M3(M4(M5M6)))))。除此之外还有
(
M
1
(
M
2
(
M
3
M
4
)
(
M
5
M
6
)
)
)
(M_1(M_2(M_3M_4)(M_5M_6)))
(M1(M2(M3M4)(M5M6)))等等,计算顺序多种多样。不同顺序计算结果完全相同,但乘法运算次数会有所差异。
处理矩阵链乘法问题时,如果检查所有的运算顺序那么算法复杂度将达到 O ( n ! ) O(n!) O(n!)。不过,这个问题能够分割成更小的局部问题,我们可以运用动态规划法。
首先 ( M 1 M 2 ) (M_1M_2) (M1M2)只有一种计算方法(顺序),需要 p 0 ∗ p 1 ∗ p 2 p_0*p_1*p_2 p0∗p1∗p2次乘法运算。同理, ( M 2 M 3 ) (M_2M_3) (M2M3)也只有一种计算方法,需要 p 1 ∗ p 2 ∗ p 3 p_1*p_2*p_3 p1∗p2∗p3次乘法运算。归纳后可知, ( M i M i + 1 ) (M_iM_{i+1}) (MiMi+1)只有一种计算方法,需要 p i − 1 ∗ p i ∗ p i + 1 p_{i-1}*p_i*p_{i+1} pi−1∗pi∗pi+1次乘法运算。
接下来求 ( M 1 M 2 M 3 ) 、 ( M 2 M 3 M 4 ) 、 . . . 、 ( M n − 2 M n − 1 M n ) (M_1M_2M_3)、(M_2M_3M_4)、...、(M_{n-2}M_{n-1}M_n) (M1M2M3)、(M2M3M4)、...、(Mn−2Mn−1Mn)的最优计算方法。计算 ( M 1 M 2 M 3 ) (M_1M_2M_3) (M1M2M3)的最优计算方法时,我们要分别算出 ( M 1 ( M 2 M 3 ) ) (M_1(M_2M_3)) (M1(M2M3))与 ( ( M 1 M 2 ) M 3 ) ((M_1M_2)M_3) ((M1M2)M3)的成本,取其中较小的一个作为 ( M 1 M 2 M 3 ) (M_1M_2M_3) (M1M2M3)的成本记录在表中。
(
M
1
(
M
2
M
3
)
)
=
(
M
1
)
+
(
M
2
M
3
)
+
p
0
∗
p
1
∗
p
3
(M_1(M_2M_3))=(M_1)+(M_2M_3)+p_0*p_1*p_3
(M1(M2M3))=(M1)+(M2M3)+p0∗p1∗p3
(
(
M
1
M
2
)
M
3
)
=
(
M
1
M
2
)
+
(
M
3
)
+
p
0
∗
p
2
∗
p
3
((M_1M_2)M_3)=(M_1M_2)+(M_3)+p_0*p_2*p_3
((M1M2)M3)=(M1M2)+(M3)+p0∗p2∗p3
这一步用到的 ( M 1 M 2 ) (M_1M_2) (M1M2)和 ( M 2 M 3 ) (M_2M_3) (M2M3)的成本可以直接从表中引用,不需要再进行计算。另外还要注意,当1 ≤ i ≤ n时, ( M i ) (M_i) (Mi)的成本为0。
一般情况,矩阵链乘法 ( M i M i + 1 . . . M j ) (M_iM_{i+1}...M_j) (MiMi+1...Mj)的最优解就是 ( M i M i + 1 . . . M j ) ( M k + 1 . . . M j ) (M_iM_{i+1}...M_j)(M_{k+1}...M_j) (MiMi+1...Mj)(Mk+1...Mj)的最小成本(其中i ≤ k ≤ j)。
例, ( M 1 M 2 M 3 M 4 M 5 ) ( i = 1 , j = 5 时 ) (M_1M_2M_3M_4M_5)(i=1,j=5时) (M1M2M3M4M5)(i=1,j=5时)的最优解就是下列式子中的最小值。
(
M
1
)
(
M
2
M
3
M
4
M
5
)
=
(
M
1
)
+
(
M
2
M
3
M
4
M
5
)
+
p
0
∗
p
1
∗
p
5
(
k
=
1
时
)
(M_1)(M_2M_3M_4M_5)=(M_1)+(M_2M_3M_4M_5)+p_0*p_1*p_5(k=1时)
(M1)(M2M3M4M5)=(M1)+(M2M3M4M5)+p0∗p1∗p5(k=1时)
(
M
1
M
2
)
(
M
3
M
4
M
5
)
=
(
M
1
M
2
)
+
(
M
3
M
4
M
5
)
+
p
0
∗
p
2
∗
p
5
(
k
=
2
时
)
(M_1M_2)(M_3M_4M_5)=(M_1M_2)+(M_3M_4M_5)+p_0*p_2*p_5(k=2时)
(M1M2)(M3M4M5)=(M1M2)+(M3M4M5)+p0∗p2∗p5(k=2时)
(
M
1
M
2
M
3
)
(
M
4
M
5
)
=
(
M
1
M
2
M
3
)
+
(
M
4
M
5
)
+
p
0
∗
p
3
∗
p
5
(
k
=
3
时
)
(M_1M_2M_3)(M_4M_5)=(M_1M_2M_3)+(M_4M_5)+p_0*p_3*p_5(k=3时)
(M1M2M3)(M4M5)=(M1M2M3)+(M4M5)+p0∗p3∗p5(k=3时)
(
M
1
M
2
M
3
M
4
)
(
M
5
)
=
(
M
1
M
2
M
3
M
4
)
+
(
M
5
)
+
p
0
∗
p
4
∗
p
5
(
k
=
4
时
)
(M_1M_2M_3M_4)(M_5)=(M_1M_2M_3M_4)+(M_5)+p_0*p_4*p_5(k=4时)
(M1M2M3M4)(M5)=(M1M2M3M4)+(M5)+p0∗p4∗p5(k=4时)
现在来看看这个算法的具体实现。先准备下述变量
m[n+1][n+1]:该二维数组中,m[i][j]表示计算
(
M
i
M
i
+
1
.
.
.
M
j
)
(M_iM_{i+1}...M_j)
(MiMi+1...Mj)时所需乘法运算的最小次数。
p[n+1]:该一维数组用于存储矩阵的行列数,其中M_i是p[i-1] * p[i]的矩阵。
利用上述变量,我们可以通过下式求出m[i][j]。
m [ i ] [ j ] = { 0 i f i = j m i n i ≤ k < j ( m [ i ] [ k ] + m [ k + 1 ] [ j ] + p [ i − 1 ] ∗ p [ k ] ∗ p [ j ] i f i < j m[i][j]= \begin{cases} 0 & if&i=j \\ min_{i≤k<j}(m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] & if & i<j \end{cases} m[i][j]={0mini≤k<j(m[i][k]+m[k+1][j]+p[i−1]∗p[k]∗p[j]ififi=ji<j
算法实现方法如下。
matrixChainMultiplication()
for i = 1 to n
m[i][i] = 0
for l = 2 to n
for i = 1 to n - l + 1
j = i + l - 1
m[i][j] = INFTY
for k = i to j - 1
m[i][j] = min(m[i][j], m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j])
AC代码如下
#include<iostream>
#include<algorithm>
using namespace std;
static const int N = 100;
int main(){
int n, p[N + 1], m[N + 1][N + 1];
cin>>n;
for(int i = 1; i <= n; i++){
cin>>p[i - 1]>>p[i];
}
for(int i = i; i <= n; i++) m[i][i] = 0;
for(int l = 2; l <= n; l++){
for(int i = 1; i <= n - l + 1; i++){
int j = i + l - 1;
m[i][j] = (1 << 21);
for(int k = i; k <= j - 1; k++){
m[i][j] = min(m[i][j], m[i][k] + m[k + 1][j] + p[i - 1] * p[k] *p[j]);
}
}
}
cout<<m[1][n]<<endl;
return 0;
}