#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;
ifstream f("matrice2.in");
ofstream g("matrice2.out");
#define MAXN 305
#define MAXNN 90050
#define MAXVAL 1000005
#define MAXQ 40005
struct punct{
int x, y;
} q[MAXQ];
int N, Q, mat[MAXNN], val[MAXVAL], T[MAXNN], R[MAXNN], gasit[MAXQ];
vector<int> sortat;
int Find(int x)
{
int aux, q = x;
while (q != T[q]) q = T[q];
while (x != T[x])
{
aux = T[x];
T[x] = q;
x = aux;
}
return q;
}
void Unite(int x, int y)
{
if (R[x] < R[y]) T[x] = y, R[y] += R[x], R[x] = R[y];
else T[y] = x, R[x] += R[y], R[y] = R[x];
}
int main()
{
int i, j, left, right, middle;
f >> N >> Q; // Citeste
for (i = 1; i <= N; ++i)
for (j = 1; j <= N; ++j)
f >> mat[N*(i - 1) + j], val[mat[N*(i - 1) + j]]++;
for (i = 1; i <= Q; ++i)
f >> q[2 * i].x >> q[2 * i].y >> q[2 * i + 1].x >> q[2 * i + 1].y; // [pozitii (>=2)] PARE - punct de plexare ; IMPARE - punct destinatie
for (i = 1; i <= MAXVAL - 4; ++i) // In loc de sortare
if (val[i])
sortat.push_back(i);
// Varianta neoptimizata de 35 de puncte
for (i = 1; i <= Q; i++)
{
left = gasit[i], right = sortat.size() - 1;
while (left < right)
{
for (j = 1; j <= N*N; j++)
T[j] = j, R[j] = 1;
middle = (left + right) / 2;
for (j = 1; j <= N*N; ++j) {
if (mat[j] >= sortat[middle])
{
if (j%N != 0 && mat[j+1] >= sortat[middle] && Find(j) != Find(j + 1))
Unite(Find(j), Find(j + 1));
if (j <= (N-1)*N && mat[j + N] >= sortat[middle] && Find(j) != Find(j + N))
Unite(Find(j), Find(j + N));
}
}
if (Find((q[2*i].x - 1) * N + q[2*i].y) == Find((q[2*i + 1].x - 1) * N + q[2*i + 1].y))
gasit[i] = middle, left = middle + 1;
else
right = middle;
}
g << sortat[gasit[i]] << '\n';
}
return 0;
}