code: def matrix_mult_opt_order(M):
      
      n = len(M)
      r = [len(Mi) for Mi in M]
      c = [len(Mi[0]) for Mi in M]
      opt = [[0 for j in range(n)] for i in range(n)]
      arg = [[None for j in range(n)] for i in range(n)]
      for j_i in range(1, n):   # loop on i, j of increasing j - i = j_i
          for i in range(n - j_i):
              j = i + j_i
              opt[i][j] = float('inf')
              for k in range(i, j):
                  alt = opt[i][k] + opt[k + 1][j] + r[i] * c[k] * c[j]
                  if opt[i][j] > alt:
                      opt[i][j] = alt
                      arg[i][j] = k
      return opt, arg
docs: Matrix chain multiplication optimal order
  
      :param M: list of matrices
      :returns: matrices opt, arg, such that opt[i][j] is the optimal number of
                operations to compute M[i] * ... * M[j] when done in the order
                (M[i] * ... * M[k]) * (M[k + 1] * ... * M[j]) for k = arg[i][j]
      :complexity: :math:`O(n^2)`