#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define Nmax 310
#define Qmax 20010
const int DX[4] = {-1, 0, 0, 1};
const int DY[4] = {0, -1, 1, 0};
int N, M, Mat[Nmax][Nmax], A[Nmax][Nmax];
int Father[Nmax * Nmax], AnsQ[Qmax];
struct Cell
{
int X, Y, Val;
}V[Nmax * Nmax];
struct Query
{
int X1, Y1, X2, Y2, Ans, Pos;
}Q[Qmax];
struct CmpCells
{
bool operator() (Cell A, Cell B)
{
return A.Val > B.Val;
}
};
struct CmpQueries
{
bool operator() (Query A, Query B)
{
return A.Ans > B.Ans;
}
};
int Root(int X)
{
int Y = X;
for(; Father[X] != X; X = Father[X]);
while(Y != X)
{
int Aux = Father[Y];
Father[Y] = X;
Y = Aux;
}
return X;
}
void Merge(int X, int Y)
{
if(X % 2 == 0) Father[X] = Y;
else Father[Y] = X;
}
void AddCells(int IdxCell, int MinVal)
{
int X = V[IdxCell].X, Y = V[IdxCell].Y, k;
for(k = 0; k < 4; ++ k)
{
int NextX = X + DX[k], NextY = Y + DY[k];
if(1 <= NextX && NextX <= N && 1 <= NextY && NextY <= N && A[NextX][NextY] >= MinVal)
{
int RootA = Root(Mat[X][Y]), RootB = Root(Mat[NextX][NextY]);
if(RootA != RootB) Merge(RootA, RootB);
}
}
}
int main()
{
freopen("matrice2.in", "r", stdin);
freopen("matrice2.out", "w", stdout);
int i, j, K = 0;
scanf("%i %i", &N, &M);
for(i = 1; i <= N; ++ i)
for(j = 1; j <= N; ++ j)
{
scanf("%i", &A[i][j]);
V[++K].Val = A[i][j];
V[K].X = i;
V[K].Y = j;
Mat[i][j] = K;
}
for(i = 1; i <= M; ++ i) scanf("%i %i %i %i", &Q[i].X1, &Q[i].Y1, &Q[i].X2, &Q[i].Y2), Q[i].Pos = i;
sort(V + 1, V + N * N + 1, CmpCells());
int Bit = 0;
for(; (1 << Bit) <= V[1].Val; Bit ++);
Bit --;
for(; Bit >= 0; Bit --)
{
for(i = 1; i <= N * N; ++ i) Father[i] = i;
sort(Q + 1, Q + M + 1, CmpQueries());
j = 1;
for(i = 1; i <= M; ++ i)
{
int NewAns = Q[i].Ans + (1 << Bit);
for(; j <= N * N && V[j].Val >= NewAns; ++ j) AddCells(j, NewAns);
int RootA = Root(Mat[Q[i].X1][Q[i].Y1]), RootB = Root(Mat[Q[i].X2][Q[i].Y2]);
if(RootA == RootB) Q[i].Ans = NewAns;
}
}
for(i = 1; i <= M; ++ i) AnsQ[Q[i].Pos] = Q[i].Ans;
for(i = 1; i <= M; ++ i) printf("%i\n", AnsQ[i]);
return 0;
}