#include<bits/stdc++.h>
using namespace std;
using ll = long long;

// #undef DEBUG
// #define DEBUG
/// {{{ DEBUG --- ///
#ifdef DEBUG
template <typename T> ostream &operator<<(ostream &o, const vector<T> &v) { o << "{"; for(size_t i = 0; i < v.size(); i++) o << v[i] << (i + 1 != v.size() ? ", " : ""); o << "}"; return o; }
#ifdef USE_COUT
#define dump(...) [&](){auto __debug_tap=make_tuple(__VA_ARGS__);cout<<"["<<__LINE__<< "] "<<#__VA_ARGS__<<" = "<<__debug_tap<<"\n";}()
#else
#define dump(...) [&](){auto __debug_tap=make_tuple(__VA_ARGS__);cout<<"["<<__LINE__<< "] "<<#__VA_ARGS__<<" = "<<__debug_tap<<"\n";}()
#endif
template<int n, class...T> typename enable_if<(n>=sizeof...(T))>::type _ot(ostream &, tuple<T...> const &){}
template<int n, class...T> typename enable_if<(n< sizeof...(T))>::type _ot(ostream & os, tuple<T...> const & t){ os << (n==0?"":", ") << get<n>(t); _ot<n+1>(os, t); }
template<class...T> ostream & operator<<(ostream &o, tuple<T...> const &t){ o << "("; _ot<0>(o, t); o << ")"; return o; }
template<class T, class U> ostream & operator<<(ostream &o, pair<T, U> const &p) { o << "(" << p.first << ", " << p.second << ")"; return o; }
template<class T> inline void dump2D(T &d, size_t sizey, size_t sizex) { ostream&o=
#ifdef USE_COUT
  cout;
#else
  cerr;
#endif
  for(size_t i = 0; i < sizey; i++) { for(size_t j = 0; j < sizex; j++) o << d[i][j] << " "; o << endl; } }
#else
  template <typename T> ostream &operator<<(ostream &o, const vector<T> &v) { for(size_t i = 0; i < v.size(); i++) o << v[i] << (i + 1 != v.size() ? " " : ""); return o; }
#define dump(...) (42)
#define dump2D(...) (42)
#endif
  /// }}}--- ///
  

const int inf = numeric_limits<int>::max();

// constructor(int sizex, int sizey, T identity)
/// --- 2D SegmentTree Library {{{ ///

template<class T>
struct SegTree2D {
  vector<T> dat;
  int sizex, sizey;
  T identity;
  function<T(T, T)> merge;
  SegTree2D(int tx, int ty, T identity, function<T(T, T)> merge)
    :identity(identity), merge(merge) {
      sizex = sizey = 1;
      while(tx > sizex) sizex <<= 1;
      while(ty > sizey) sizey <<= 1;
      dat = vector<T>(sizex * sizey * 4, identity);
    }
  void set(int x, int y, T const &val) {
    int kx = x + sizex - 1, ky = y + sizey - 1;
    dat[kx * sizey * 2 + ky] = val;
    while(ky > 0) {
      ky = (ky - 1) / 2;
      dat[kx * sizey * 2 + ky] = merge(
          dat[kx * sizey * 2 + ky * 2 + 1],
          dat[kx * sizey * 2 + ky * 2 + 2]
          );
    }
    while(kx > 0) {
      kx = (kx - 1) / 2;
      ky = y + sizey - 1;
      dat[kx * sizey * 2 + ky] = merge(
          dat[(kx * 2 + 1) * sizey * 2 + ky],
          dat[(kx * 2 + 2) * sizey * 2 + ky]
          );
      while(ky > 0) {
        ky = (ky - 1) / 2;
        dat[kx * sizey * 2 + ky] = merge(
            dat[kx * sizey * 2 + ky * 2 + 1],
            dat[kx * sizey * 2 + ky * 2 + 2]
            );
      }
    }
  }
  void act(int x, int y, T const &val) {
    int kx = x + sizex - 1, ky = y + sizey - 1;
    set(x, y, merge(dat[kx * sizey * 2 + ky], val));
  }
private:
  // void setx(int x, int y, T const &val,
  //     int sxl, int sxr, int k, int set) {
  //   if(x+1 <= sxl || sxr <= x) return;
  //   if(x <= sxl && sxr <= x+1) {
  //     sety(k, y, val, 0, sizey, 0, set);
  //     return;
  //   }
  //   setx(x, y, val, sxl, (sxl + sxr) / 2, k * 2 + 1, set);
  //   setx(x, y, val, (sxl + sxr) / 2, sxr, k * 2 + 2, set);
  // }
  // void setx(int kx, int y, T const &val,
  //     int syl, int syr, int k, int set) {
  //   if(y+1 <= syl || syr <= y) return;
  //   if(y <= syl && syr <= y+1) {
  //     if(set) dat[kx * sizey * 2 + k] = val;
  //     else dat[kx * sizey * 2 + k] = merge(dat[k * ], val);
  //     return;
  //   }
  //   if(sxr - sxl < syr - syl) {
  //     // devide y
  //     setx(x, y, val, sxl, sxr, syl, (syl + syr) / 2, k * 2 + 1, set);
  //     goone(x, y, val, sxl, sxr, (syl + syr) / 2, syr, k * 2 + 2, set);
  //   } else {
  //     // devide x
  //     goone(x, y, val, sxl, (sxl + sxr) / 2, syl, syr, k * 2 + 1, set);
  //     goone(x, y, val, (sxl + sxr) / 2, sxr, syl, syr, k * 2 + 2, set);
  //   }
  //   dat[k] = merge(dat[k * 2 + 1], dat[k * 2 + 2]);
  // }
public:
  T query(int xl, int xr, int yl, int yr) {
    return queryx(xl, xr, yl, yr, 0, sizex, 0);
  }
private:
  T queryx(int xl, int xr, int yl, int yr,
      int sxl, int sxr, int k) {
    if(xr <= sxl || sxr <= xl) return identity;
    if(xl <= sxl && sxr <= xr) return queryy(k, yl, yr, 0, sizey, 0);
    return merge(
        queryx(xl, xr, yl, yr, sxl, (sxl + sxr) / 2, k * 2 + 1),
        queryx(xl, xr, yl, yr, (sxl + sxr) / 2, sxr, k * 2 + 2)
        );
  }
  T queryy(int kx, int yl, int yr,
      int syl, int syr, int k) {
    if(yr <= syl || syr <= yl) return identity;
    if(yl <= syl && syr <= yr) return dat[kx * sizey * 2 + k];
    return merge(
        queryy(kx, yl, yr, syl, (syl + syr) / 2, k * 2 + 1),
        queryy(kx, yl, yr, (syl + syr) / 2, syr, k * 2 + 2)
        );
  }
};

/// }}}--- ///

int main() {
  ios::sync_with_stdio(false), cin.tie(0);
  while(1) {
    int h,w,q;
    cin>>h>>w>>q;
    if(h==0) break;

    SegTree2D<int> ecasdqina(h, w, inf, [&](int a, int b){return a < b ? a : b;});
    for(int i = 0; i < h; i++) for(int j = 0; j < w; j++) {
      int x;
      cin >> x;
      ecasdqina.set(i, j, x);
    }
    for(int i = 0; i < q; i++) {
      int x1, y1, x2, y2;
      cin >> x1 >> y1 >> x2 >> y2;
      cout << ecasdqina.query(x1, x2 + 1, y1, y2 + 1) << endl;
    }
  }
  return 0;
}



Measured memory_bytes: 35580