矩阵链相乘(动态规划)

【问题描述】给定n个矩阵 M 1 , M 2 . . . M n M_1,M_2...M_n M1,M2...Mn,他们的维数分别是 r 1 ∗ c 1 , r 2 ∗ c 2 . . . r n ∗ c n r_1*c_1,r_2*c_2...r_n*c_n r1c1,r2c2...rncn,要求使用【动态规划】的策略求解矩阵连乘的最优计算代价(总乘法次数最少)。题目保证矩阵相乘一定是有效的。

例如有三个矩阵 M 1 , M 2 , M 3 M_1,M_2,M_3 M1M2M3,他们的维度分别是 2 ∗ 10 , 10 ∗ 2 , 2 ∗ 10 2*10,10*2,2*10 210,102210。按照矩阵乘法的结合律,可以先把 M 1 M_1 M1 M 2 M_2 M2相乘,然后把结果和 M 3 M_3 M3相乘,总的乘法次数为 2 ∗ 10 ∗ 2 + 2 ∗ 2 ∗ 10 = 80 2*10*2+2*2*10=80 2102+2210=80次;也可以先把 M 2 M_2 M2 M 3 M_3 M3相乘,再用 M 1 M_1 M1去相乘,这种方式下总的乘法次数为 10 ∗ 2 ∗ 10 + 2 ∗ 10 ∗ 10 = 400 10*2*10+2*10*10=400 10210+21010=400次。因此最优计算代价为80。
【输入形式】输入的第1行中有1个数字 n n n,表示矩阵的个数;接下来 n n n行,每行2个整数 r i r_i ri c i c_i ci,分别表示矩阵 M i M_i Mi的行数和列数。
【输出形式】输出1行中有一个数字,表示 n n n个矩阵相乘的最优计算代价。
【样例输入】

3

2 10

10 2

2 10
【样例输出】

80
【说明】

n > = 2 n>=2 n>=2

1 < = r i , c i < = 20 1<=r_i,c_i<=20 1<=ri,ci<=20

题解:

我们定义 d p [ i ] [ j ] dp[i][j] dp[i][j]为从第 i i i个矩阵连乘到第 j j j个矩阵的最优代价,那么我们可以得到状态转移方程

∀ k ∈ [ i , j ) , d p [ i ] [ j ] = d p [ i ] [ k ] + d p [ k + 1 ] [ j ] + r [ i ] ∗ c [ k ] ∗ c [ j ] \forall k \in [i,j),dp[i][j]=dp[i][k]+dp[k+1][j]+r[i]*c[k]*c[j] k[i,j),dp[i][j]=dp[i][k]+dp[k+1][j]+r[i]c[k]c[j]

解释一下,这个方程的意思就是从第 i i i号矩阵到第 j j j号矩阵的连乘的最优代价等于 i i i个矩阵到第 k k k个矩阵的最优代价加上 k + 1 k+1 k+1到第 j j j个矩阵的最优代价再加上这两边矩阵相乘的代价,其中 k k k是大于等于 i i i且小于 j j j的一个正整数。

更通俗地来说,第 k k k个矩阵作为第 i i i号矩阵到第 j j j号矩阵的一个分割线,把整条矩阵链分成了两部分,那么总的最优代价就等于左边部分的最优代价加上右边部分的最优代价,再加上把左右两部分拼回去的代价。
矩阵链相乘(动态规划)_第1张图片
相当于说,我分割好后,先把左边的矩阵链按照最优的方法计算完得到一个矩阵A,并把代价记下来,再把右边的矩阵链也按照最优的方法计算完得到另一个矩阵B,也把代价记下来,最终,将A与B相乘,这也会产生代价,最后把这三个代价相加,就得到整条矩阵链计算的代价。

但是,不同的 k k k会得到不同的代价,因此要在k的取值范围内枚举并计算将每一个 k k k作为分割线时,所产生的代价是多少,并取最小值作为整条矩阵链的最优代价。

说了这么多,可能还需要补充一下矩阵相乘的代价需要怎样计算,举例来说,一个 1 × 3 1\times3 1×3的矩阵和一个 3 × 5 3\times5 3×5的矩阵相乘所产生的计算代价就是 1 × 3 × 5 1\times3\times5 1×3×5;一个 1 × 5 1\times5 1×5的矩阵和一个 5 × 5 5\times5 5×5的矩阵相乘所产生的计算代价就是 1 × 5 × 5 1\times5\times5 1×5×5

根据以上举例,可以总结出规律:

一个 n 1 × m 1 n_1 \times m_1 n1×m1的矩阵和一个 n 2 × m 2 n_2 \times m_2 n2×m2的矩阵相乘所产生的计算代价就是 n 1 × m 1 × m 2 n_1 \times m_1 \times m_2 n1×m1×m2,当然,众所周知,两个矩阵相乘的必要条件是 m 1 = n 2 m_1=n_2 m1=n2,所以把 m 1 m_1 m1替换成 n 2 n_2 n2也是ok的。

其实这个代价仅代表的是两个矩阵相乘所需要使用乘法的次数,我们知道,矩阵相乘不仅需要乘法,也需要加法,但如果单单只是想要比较计算代价来得出最优方法,省略掉加法代价也无伤大雅。

另外,对于一个矩阵链 M 1 × M 2 × M 3 × . . . × M n M_1\times M_2 \times M_3\times...\times M_n M1×M2×M3×...×Mn来说,它的答案矩阵的规模一定是 r 1 × c n r_1\times c_n r1×cn

明确了状态转移方程的意义之后,便需要知道初始状态的情形

  • 由于矩阵本身不需要计算,因此所有的 d p [ i ] [ i ] dp[i][i] dp[i][i]都等于0
  • 考虑矩阵链中只包含两个矩阵的情形,那么 d p [ 1 ] [ 2 ] = d p [ 1 ] [ 1 ] + d p [ 2 ] [ 2 ] + r [ 1 ] ∗ r [ 1 ] ∗ c [ 2 ] dp[1][2]=dp[1][1]+dp[2][2]+r[1]*r[1]*c[2] dp[1][2]=dp[1][1]+dp[2][2]+r[1]r[1]c[2]
  • 如果我们画出 d p dp dp表可以看到 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1] d p [ 2 ] [ 2 ] dp[2][2] dp[2][2]是位于 d p [ 1 ] [ 2 ] dp[1][2] dp[1][2]的左下角的,甚至来说,对于任意一个 d p [ i ] [ j ] dp[i][j] dp[i][j]来说,它的求解都需要用到它左下方的值,如果按照一般的 d p dp dp路线,从第一行开始,每一行从第一个开始向右枚举,那么 d p [ i ] [ j ] dp[i][j] dp[i][j]左下方的值根本就没有求解过,何谈根据最优子结构递推得出最优解?

因此,我们dp的路线也有所改变,具体做法代码中已给出,外层循环枚举每一条对角线 d d d,第二层循环枚举行号 i i i,根据行号 i i i和对角线号 d d d可以得出列号 t = i + d − 1 t=i+d-1 t=i+d1,最后一层循环枚举 k k k
矩阵链相乘(动态规划)_第2张图片

代码:

#include 
using namespace std;
const int inf = 0x3f3f3f3f;
int n, r[21], c[21], dp[21][21];
//def:dp[i][j]代表从第i个矩阵到第j个矩阵的连乘的最优代价

void init() {
    for (int i = 1; i <= n; i++) {
        dp[i][i] = 0;
    }
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> r[i] >> c[i];
    }
    init();
    for (int d = 2; d <= n; d++) {
        for (int i = 1; i <= n - d + 1; i++) {
            int t = i + d - 1; dp[i][t] = inf;
            for (int k = i; k < t; k++) {
                int cost = dp[i][k] + dp[k + 1][t] + r[i] * c[k] * c[t];
                if (cost < dp[i][t]) {
                    dp[i][t] = cost;
                }
            }
        }
    }
    cout << dp[1][n];
}

你可能感兴趣的:(动态规划,矩阵,算法)