#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <set>
#define pii pair <int, int>
using namespace std;
ifstream fin("matrice2.in");
ofstream fout("matrice2.out");
const int NMAX = 300, QMAX = 2e4, VMAX = 1e6;
struct Query{
int x1, y1, x2, y2;
int st, dr, mij;
int ans, ind;
bool operator < (const Query &other) const {
return ind < other.ind;
}
};
struct DSU{
int sz[NMAX + 1][NMAX + 1];
pii parent[NMAX + 1][NMAX + 1];
void clear(int n){
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
parent[i][j] = {0, 0};
}
void init(int n){
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
parent[i][j] = {i, j}, sz[i][j] = 1;
}
void init(int i, int j){
parent[i][j] = {i, j};
sz[i][j] = 1;
}
pii Find(int i, int j){
if(parent[i][j].first == i && parent[i][j].second == j)
return parent[i][j];
return parent[i][j] = Find(parent[i][j].first, parent[i][j].second);
}
void Union(pii A, pii B){
pii parentA = Find(A.first, A.second);
pii parentB = Find(B.first, B.second);
if((parentA == parentB) || parentA.first == 0 || parentB.first == 0)
return;
if(sz[parentA.first][A.second] < sz[parentB.first][parentB.second])
swap(parentA, parentB);
sz[parentA.first][parentA.second] += sz[parentB.first][parentB.second];
parent[parentB.first][parentB.second] = parentA;
}
// N, E, S, V
int diri[4] = {-1, 0, 1, 0};
int dirj[4] = {0, 1, 0, -1};
bool inB(int i, int j, int n){
return (1 <= i && i <= n && 1 <= j && j <= n);
}
void Insert(int i, int j, int n){
init(i, j);
for(int dir = 0; dir < 4; dir++){
int newI = i + diri[dir], newJ = j + dirj[dir];
if(inB(newI, newJ, n))
Union({i, j}, {newI, newJ});
}
}
bool Conex(pii A, pii B){
pii parentA = Find(A.first, A.second);
pii parentB = Find(B.first, B.second);
return (parentA == parentB && parentA.first != 0);
}
};
int n, q, mat[NMAX + 1][NMAX + 1];
vector <pii> pos[VMAX + 1];
vector <int> vals;
set <int> S;
DSU DS; // <3
vector <Query> queries;
void parallelThreadBinarySearch(){
int K = vals.size();
while(K > 0){
K /= 2;
vector <Query> newQueries, others;
DS.clear(n);
int ind = 0;
for(int i = (int)vals.size() - 1; i >= 0; i--){
for(auto p : pos[vals[i]])
DS.Insert(p.first, p.second, n);
others.clear();
while(ind < q - 1 && queries[ind].mij > i)
ind++;
while(ind <= q - 1 && queries[ind].mij == i){
if(DS.Conex({queries[ind].x1, queries[ind].y1}, {queries[ind].x2, queries[ind].y2})){
queries[ind].st = queries[ind].mij + 1,
queries[ind].mij = (queries[ind].st + queries[ind].dr) / 2;
queries[ind].ans = vals[i];
newQueries.push_back(queries[ind]);
}else{
queries[ind].dr = queries[ind].mij - 1;
queries[ind].mij = (queries[ind].st + queries[ind].dr) / 2;
others.push_back(queries[ind]);
}
ind++;
}
for(Query query : others)
newQueries.push_back(query);
}
queries = newQueries;
}
}
int main(){
fin >> n >> q;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
fin >> mat[i][j], S.insert(mat[i][j]), pos[mat[i][j]].push_back({i, j});
for(auto it : S)
vals.push_back(it);
for(int i = 1; i <= q; i++){
Query query;
fin >> query.x1 >> query.y1 >> query.x2 >> query.y2, query.ind = i;
query.st = 0, query.dr = (int)vals.size() - 1;
query.mij = (query.st + query.dr) / 2;
queries.push_back(query);
}
parallelThreadBinarySearch();
sort(queries.begin(), queries.end());
for(auto query : queries)
fout << query.ans << '\n';
return 0;
}