#include <bits/stdc++.h>
using namespace std;
struct RollbackDSU {
struct State {
int x_, x, rnk_x, y_, y, rnk_y;
};
int n, m;
vector<int> dad, rnk;
vector<State> ops;
RollbackDSU(int n_, int m_) : n(n_), m(m_), dad(n * m, -1), rnk(n * m) {}
int Find(int x) {
if (dad[x] == -1) return x;
return Find(dad[x]);
}
void Union(int x, int y) {
int x_ = x, y_ = y;
x = Find(x), y = Find(y);
if (x == y) return;
ops.emplace_back(State{x_, x, rnk[x], y_, y, rnk[y]});
if (rnk[x] > rnk[y]) swap(x, y);
dad[x] = y;
rnk[y] += rnk[x] == rnk[y];
}
void Rollback(int x, int y) {
if (ops.empty()) return;
if (ops.back().x_ != x || ops.back().y_ != y) return;
x = ops.back().x, y = ops.back().y;
dad[x] = dad[y] = -1;
rnk[x] = ops.back().rnk_x;
rnk[y] = ops.back().rnk_y;
ops.pop_back();
}
void Union(pair<int, int> x, pair<int, int> y) {
Union(x.first * n + x.second, y.first * n + y.second);
}
int Find(pair<int, int> x) {
return Find(x.first * n + x.second);
}
void Rollback(pair<int, int> x, pair<int, int> y) {
Rollback(x.first * n + x.second, y.first * n + y.second);
}
};
const int dx[] = {-1, 0, 1, 0};
const int dy[] = {0, 1, 0, -1};
int main() {
ifstream cin("matrice2.in");
ofstream cout("matrice2.out");
int n, q; cin >> n >> q;
vector<vector<int>> a(n, vector<int>(n));
vector<tuple<int, int, int>> v;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
cin >> a[i][j];
v.emplace_back(a[i][j], i, j);
}
}
vector<pair<pair<int, int>, pair<int, int>>> qrs(q);
for (int i = 0; i < q; ++i) {
pair<int, int> a, b; cin >> a.first >> a.second >> b.first >> b.second;
--a.first, --a.second, --b.first, --b.second;
qrs[i] = make_pair(a, b);
}
sort(v.rbegin(), v.rend());
RollbackDSU dsu(n, n);
for (int i = 0; i < n * n; ++i) {
int x, y; tie(ignore, x, y) = v[i];
a[x][y] = i;
}
auto Mid = [](int x, int y) {
return x + (y - x) / 2;
};
auto Check = [&](int idx) {
pair<int, int> one, two; tie(one, two) = qrs[idx];
return dsu.Find(one) == dsu.Find(two);
};
auto Inside = [&](int x, int y) {
return 0 <= x && x < n &&
0 <= y && y < n;
};
auto Activate = [&](int l, int r) {
for (int i = l; i <= r; ++i) {
int val, x, y; tie(val, x, y) = v[i];
for (int dir = 0; dir < 4; ++dir) {
int nx = x + dx[dir], ny = y + dy[dir];
if (Inside(nx, ny) && a[nx][ny] < a[x][y]) {
dsu.Union({x, y}, {nx, ny});
}
}
}
};
auto Deactivate = [&](int l, int r) {
for (int i = r; i >= l; --i) {
int val, x, y; tie(val, x, y) = v[i];
for (int dir = 3; dir >= 0; --dir) {
int nx = x + dx[dir], ny = y + dy[dir];
if (Inside(nx, ny) && a[nx][ny] < a[x][y]) {
dsu.Rollback({x, y}, {nx, ny});
}
}
}
};
vector<int> ans(q);
function<void(int, int, vector<int>&)>
Solve = [&](int lo, int hi, vector<int> &to_solve) {
if (lo == hi) {
for (int &idx : to_solve) {
ans[idx] = get<0>(v[lo]);
}
return;
}
int mid = Mid(lo, hi);
vector<int> lft, rgt;
for (int &x : to_solve) {
if (Check(x))
lft.emplace_back(x);
else
rgt.emplace_back(x);
}
to_solve.clear();
int lft_mid = Mid(lo, mid);
Deactivate(lft_mid + 1, mid);
Solve(lo, mid, lft);
int rgt_mid = Mid(mid + 1, hi);
Activate(mid + 1, rgt_mid);
Solve(mid + 1, hi, rgt);
};
int mid = (n * n - 1) / 2;
Activate(0, mid);
vector<int> to_solve(q);
iota(to_solve.begin(), to_solve.end(), 0);
Solve(0, n * n - 1, to_solve);
for (int i = 0; i < q; ++i) {
cout << ans[i] << '\n';
}
cout << endl;
}