#include <cstdio>
#include <algorithm>
using namespace std;
const int nmx = 302;
const int p1[] = {0,0,1,-1}, p2[] = {1,-1,0,0};
struct nod
{
int i,j;
} v[nmx*nmx];
int n,m,mat[nmx][nmx];
int tata[nmx*nmx],rang[nmx*nmx];
struct Cmp
{
bool operator() (nod n1, nod n2)
{
return mat[n1.i][n1.j] > mat[n2.i][n2.j];
}
} cmp;
int poz(int i, int j)
{
return (i - 1) * n + j;
}
void citire()
{
scanf("%d %d", &n, &m);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= n; ++j)
{
scanf("%d", &mat[i][j]);
int p = poz(i,j);
v[p].i = i;
v[p].j = j;
}
}
void reset_paduri()
{
for(int i = 1; i <= n * n; ++i)
{
tata[i] = i;
rang[i] = 1;
}
}
int da_grupa(int ni)
{
int naux = ni;
while(tata[naux] != naux)
naux = tata[naux];
int naux2;
while(ni != tata[ni])
{
naux2 = tata[ni];
tata[ni] = naux;
ni = naux2;
}
return naux;
}
void uneste(int n1, int n2)
{
if(rang[n1] > rang[n2])
tata[n2] = n1;
else
tata[n1] = n2;
if(rang[n1] == rang[n2])
++ rang[n2];
}
bool apartine(int i, int j)
{
return i > 0 && i <= n && j > 0 && j <= n;
}
void uneste_val(int val)
{
reset_paduri();
int p = 1;
while(mat[v[p].i][v[p].j] >= val)
{
for(int q = 0; q < 4; ++q)
if(apartine(v[p].i+p1[q],v[p].j+p2[q]) && mat[v[p].i+p1[q]][v[p].j+p2[q]] >= val)
{
int g1 = da_grupa(poz(v[p].i,v[p].j));
int g2 = da_grupa(poz(v[p].i+p1[q],v[p].j+p2[q]));
if(g1 != g2)
uneste(g1,g2);
}
++p;
}
}
void queries()
{
while(m--)
{
int x1,y1,x2,y2;
scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
int maxim = -1,st = 1, dr = mat[v[1].i][v[1].j];
while(st <= dr)
{
int mij = (st + dr) / 2;
uneste_val(mij);
if(da_grupa(poz(x1,y1)) == da_grupa(poz(x2,y2)))
{
maxim = mij;
st = mij + 1;
}
else
dr = mij - 1;
}
printf("%d\n", maxim);
}
}
int main()
{
freopen("matrice2.in", "r", stdin);
freopen("matrice2.out", "w", stdout);
citire();
sort(v+1,v+n*n+1,cmp);
/**for(int i = 1; i < 26; ++i)
printf("%d %d %d\n", v[i].i, v[i].j, mat[v[i].i][v[i].j]);**/
queries();
return 0;
}