// dungeon sub 4 - parallel binary search (for loop) + dsu (fastio) (pragma)
// O((n^2 + q) * 20)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("omit-frame-pointer")
#pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x) {
char c;
for (c = getchar(); !isdigit(c); c = getchar());
x = c-'0';
for (c = getchar(); isdigit(c); c = getchar())
x = x*10 + (c-'0');
}
template<typename T>
inline void write(T x) {
T tmp = x/10, de = 1;
while (tmp > 0) { de *= 10; tmp /= 10; }
while (de > 0) { putchar(x/de+'0'); x %= de; de /= 10; }
}
template<typename T>
inline void writeln(T x) {
write(x);
putchar('\n');
}
const int N = 507;
const int QUERY = 4e5+7;
const int NODE = N*N;
const int EDGE = NODE*2;
int n, q, a[N][N];
pair<int,int> ques[QUERY];
int getId(int x, int y) {
return (x-1) * n + y;
}
struct Edge {
int x, y, w;
Edge() {}
Edge(int _x, int _y, int _w) {
x = _x; y = _y; w = _w;
}
};
int numNode, numEdge;
Edge edges[EDGE];
int fa[NODE];
int ans[QUERY], curId[QUERY], lef[QUERY], rig[QUERY];
int root(int x) {
return fa[x] < 0 ? x : fa[x] = root(fa[x]);
}
void Unite(int u, int v) {
u = root(u);
v = root(v);
if (u == v) return;
if (fa[u] > fa[v]) swap(u,v);
fa[u] += fa[v];
fa[v] = u;
}
bool sameComp(int x, int y) {
return root(x) == root(y);
}
void filter() {
memset(fa, -1, (numNode+1) * sizeof(int));
int newNum = 0;
for (int i = 0; i < numEdge; ++i) {
int u = root(edges[i].x);
int v = root(edges[i].y);
if (u != v) {
Unite(u, v);
edges[newNum++] = edges[i];
}
}
numEdge = newNum;
}
int main() {
freopen("matrice2.inp", "r", stdin);
freopen("matrice2.out", "w", stdout);
read(n);
read(q);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
read(a[i][j]);
}
for (int i = 1; i <= q; ++i) {
int x, y, u, v;
read(x); read(y);
read(u); read(v);
ques[i].first = getId(x,y);
ques[i].second = getId(u,v);
}
numNode = n * n;
numEdge = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (i < n)
edges[numEdge++] = Edge(getId(i,j), getId(i+1,j), min(a[i][j],a[i+1][j]));
if (j < n)
edges[numEdge++] = Edge(getId(i,j), getId(i,j+1), min(a[i][j],a[i][j+1]));
}
sort(edges,edges+numEdge,[&](const Edge &a, const Edge &b) {
return a.w > b.w;
});
filter();
for (int i = 1; i <= q; ++i) {
ans[i] = 0;
curId[i] = i;
}
for (int add = (1<<20); add >= 1; add>>=1) {
memset(fa, -1, (numNode+1) * sizeof(int));
int numLef = 0, numRig = 0;
for (int i = 1, j = 0; i <= q; ++i) {
while (j < numEdge && edges[j].w >= ans[curId[i]]+add) {
Unite(edges[j].x,edges[j].y);
j++;
}
if (sameComp(ques[curId[i]].first,ques[curId[i]].second)) {
ans[curId[i]] |= add;
rig[numRig++] = curId[i];
}
else {
lef[numLef++] = curId[i];
}
}
merge(lef,lef+numLef,rig,rig+numRig,curId+1,[&](int x, int y) {
return ans[x] > ans[y];
});
}
for (int i = 1; i <= q; ++i)
writeln(ans[i]);
return 0;
}