#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
FILE *fin, *fout;
#define MAXN 300
#define MAXQ 20000
#define MAXN_2 90000
#define MAXVAL 1000000
int N, Q;
int a[MAXN + 1][MAXN + 1];
int x1[MAXQ + 1], Y1[MAXQ + 1], x2[MAXQ + 1], y2[MAXQ + 1];
int st[MAXQ + 1], dr[MAXQ + 1], viz[MAXN + 1][MAXN + 1];
std::vector <int> check[MAXVAL + 1];
struct at {
int x, y;
bool operator != (const at &a) const {
if (a.x != x || a.y != y)
return 1;
return 0;
}
bool operator == (const at &a) const {
if (a.x == x && a.y == y)
return 1;
return 0;
}
};
struct util {
int x, y, val;
};
util mat[(MAXN + 1) * (MAXN + 1) + 1];
int ind;
std::queue <at> q;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};
at parent[MAXN + 1][MAXN + 1];
inline void union1(at a, at b) {
if (a.x == b.x && a.y == b.y)
return;
while (parent[a.x][a.y] != a)
a = parent[a.x][a.y];
while (parent[b.x][b.y] != b)
b = parent[b.x][b.y];
if (b.x == a.x && b.y == a.y)
return;
parent[b.x][b.y].x = a.x;
parent[b.x][b.y].y = a.y;
}
inline at find(at a) {
while (a != parent[a.x][a.y])
a = parent[a.x][a.y];
return a;
}
int nn;
inline void make(int limit) {
int ind = 1, ind1 = 2;
for (int i = 1; i <= N; i++)
for (int j = 1; j <= N; j++)
parent[i][j] = { i, j };
while (ind <= nn && mat[ind].val >= limit) {
for (int j = 0; j < 3; j++) {
int X = mat[ind].x + dx[j];
int Y = mat[ind].y + dy[j];
if (a[X][Y] >= limit) {
union1({ mat[ind].x, mat[ind].y }, { X, Y });
// printf("%d %d %d %d\n", find({ mat[ind].x, mat[ind].y }).x, find({ mat[ind].x, mat[ind].y }).y, find({ X, Y }).x, find({ X, Y }).y);
}
}
ind++;
}
}
bool cmp(util a, util b) {
return a.val > b.val;
}
int main() {
fin = fopen("matrice2.in", "r");
fout = fopen("matrice2.out", "w");
fscanf(fin, "%d%d", &N, &Q);
nn = N * N;
for(int i = 1;i <= N;i++)
for (int j = 1; j <= N; j++) {
fscanf(fin, "%d", &a[i][j]);
mat[++ind].val = a[i][j];
mat[ind].x = i;
mat[ind].y = j;
}
std::sort(mat + 1, mat + ind + 1, cmp);
for (int i = 1; i <= Q; i++) {
fscanf(fin, "%d%d%d%d", &x1[i], &Y1[i], &x2[i], &y2[i]);
st[i] = 1, dr[i] = MAXVAL;
}
bool ok = 1;
int pana;
while (ok) {
ok = 0;
pana = 0;
for (int i = 1; i <= Q; i++) {
if (st[i] != dr[i]) {
check[(st[i] + dr[i]) / 2].push_back(i), pana = std::max(pana, (st[i] + dr[i]) / 2);
// printf("%d %d %d\n", i, st[i], dr[i]);
}
}
for (int i = 1; i <= pana; i++) {//iau fiecare pas al cautarii binare si pun limita i
if(check[i].size())
make(i);
while (check[i].size()) {
ok = 1;
int who = check[i].back();//query-ul curent
check[i].pop_back();
bool ver = 0;
if (find({ x1[who], Y1[who] }) == find({ x2[who], y2[who] }))
ver = 1;
if (ver == 1) {
// printf("aa");
st[who] = i + 1;
}
else
dr[who] = i;
}
}
}
for (int i = 1; i <= Q; i++)
fprintf(fout, "%d\n", st[i] - 1);
fclose(fin);
fclose(fout);
return 0;
}