#include <bits/stdc++.h>
using namespace std;
class Parser {
private:
vector<char> str;
int ptr;
FILE *fin;
char getChar() {
if (ptr == (int) str.size()) {
fread(str.data(), sizeof(char), str.size(), fin);
ptr = 0;
}
return str[ptr++];
}
template<class T>
T getInt() {
char chr = getChar();
while (!isdigit(chr) && chr != '-')
chr = getChar();
int sgn = +1;
if (chr == '-') {
sgn = -1;
chr = getChar();
}
T nr = 0;
while (isdigit(chr)) {
nr = nr * 10 + chr - '0';
chr = getChar();
}
return nr * sgn;
}
public:
Parser(const char* name) : str(1e5), ptr(str.size()), fin(fopen(name, "r")) { }
~Parser() { fclose(fin); }
template<class T>
friend Parser& operator>>(Parser& in, T& nr) {
nr = in.getInt<T>();
return in;
}
};
Parser fin("matrice2.in");
ofstream fout("matrice2.out");
class Forest {
private:
vector<int> ans, height, father;
vector<vector<int>> qry;
void merge(int x, int y, int val) {
vector<int> arr;
qry[x].push_back(1e9);
qry[y].push_back(1e9);
for (int i = 0, j = 0; qry[x][i] < 1e9 || qry[y][j] < 1e9; )
if (qry[x][i] < qry[y][j])
arr.push_back(qry[x][i++]);
else if (qry[x][i] > qry[y][j])
arr.push_back(qry[y][j++]);
else {
ans[qry[x][i]] = val;
i++; j++;
}
qry[x].clear();
qry[y].clear();
qry[x] = arr;
}
public:
Forest(int n, int q) :
ans(q), height(n + 1), father(n + 1), qry(n + 1) { }
int find(int x) {
int root = x;
while (father[root])
root = father[root];
int aux;
while (father[x]) {
aux = father[x];
father[x] = root;
x = aux;
}
return root;
}
void unite(int x, int y, int val) {
int rootX = find(x);
int rootY = find(y);
if (rootX == rootY)
return;
if (height[rootX] < height[rootY]) {
merge(rootY, rootX, val);
father[rootX] = rootY;
}
else {
merge(rootX, rootY, val);
father[rootY] = rootX;
if (height[rootX] == height[rootY])
height[rootX]++;
}
}
void addQuery(int x, int y) {
static int id = 0;
qry[x].push_back(id);
qry[y].push_back(id);
id++;
}
vector<int> solve() {
return ans;
}
};
int main() {
int n, q; fin >> n >> q;
vector<vector<int>> mat(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
fin >> mat[i][j];
auto id = [&](int x, int y) {
return (x - 1) * n + y;
};
auto in = [&](int x, int y) {
return 1 <= x && x <= n
&& 1 <= y && y <= n;
};
Forest dsu(n * n, q);
for (int i = 0; i < q; i++) {
int x1, y1, x2, y2; fin >> x1 >> y1 >> x2 >> y2;
dsu.addQuery(id(x1, y1), id(x2, y2));
}
vector<tuple<int, int, int>> cells;
cells.reserve(n * n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cells.emplace_back(mat[i][j], i, j);
sort(cells.rbegin(), cells.rend());
const int addLin[] = {-1, 0, 1, 0};
const int addCol[] = {0, 1, 0, -1};
for (auto cell : cells) {
int x1, y1, val; tie(val, x1, y1) = cell;
for (int i = 0; i < 4; i++) {
int x2 = x1 + addLin[i];
int y2 = y1 + addCol[i];
if (in(x2, y2) && mat[x2][y2] >= mat[x1][y1])
dsu.unite(id(x1, y1), id(x2, y2), val);
}
}
auto ans = dsu.solve();
for (int i = 0; i < q; i++)
fout << ans[i] << '\n';
fout.close();
return 0;
}