#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
#include <queue>
#include <bitset>
#include <cmath>
#define Nmax 301*301
#define INF 0x3f3f3f3f
using namespace std;
priority_queue<pair<int,pair<int,int> >,
vector<pair<int,pair<int,int> > > > Heap;
int N,Q,m[305][305],daddy[Nmax],nrl;
vector<int> G[Nmax];
vector<int> lantz[Nmax];/// lanturile
vector<int> range[Nmax]; /// pentru aint
bitset<Nmax>used;
int valori[Nmax]; /// valoarea nodului i
int heavy[Nmax]; /// greutatea nodului i
int height[Nmax]; /// adancimea nodului i
int care[Nmax]; /// in care lantz se afla i
int tata_lantz[Nmax]; /// nodul de care se leaga lantzul i
int pozitie_nod[Nmax]; /// pozitia nodului i in lantzul sau
int code(int x,int y)
{
return (x-1)*N+y;
}
int INmatrix(int x,int y)
{
if( 1 <= x && x <= N &&
1 <= y && y <= N) return 1;
return 0;
}
int whos_ur_daddy(int k)
{
if(daddy[k] != k)
daddy[k] = whos_ur_daddy(daddy[k]);
return daddy[k];
}
int couple(int a,int b)
{
daddy[daddy[a]] = daddy[b];
}
class cmp{
public:
bool operator()(const int x,const int y) const
{
return heavy[x] > heavy[y];
}
};
void read()
{
scanf("%d%d",&N,&Q);
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
scanf("%d",&m[i][j]);
for(int i = 1; i <= N; ++i)
{
for(int j = 1; j <= N; ++j)
{
if(INmatrix(i+1,j))
Heap.push(make_pair(min(m[i][j],m[i+1][j]),
make_pair(code(i,j),code(i+1,j))));
if(INmatrix(i,j+1))
Heap.push(make_pair(min(m[i][j],m[i][j+1]),
make_pair(code(i,j),code(i,j+1))));
}
}
}
void kruskal()
{
int a,b,c;
for(int i = 1; i <= N*N; ++i) daddy[i] = i;
while(!Heap.empty())
{
c = Heap.top().first;
a = Heap.top().second.first;
b = Heap.top().second.second;
Heap.pop();
if(whos_ur_daddy(a) != whos_ur_daddy(b))
{
couple(a,b);
G[a].push_back(b);
G[b].push_back(a);
}
}
}
void DFSG(int k,int niv)
{
used[k] = 1;
height[k] = niv;
for(vector<int>::iterator it = G[k].begin(); it != G[k].end(); ++it)
if(!used[*it])
{
DFSG(*it,niv+1);
heavy[k] += 1 + heavy[*it];
}
}
void descomp(int k)
{
care[k] = nrl;
lantz[nrl].push_back(k);
pozitie_nod[k] = lantz[nrl].size() - 1;
used[k] = 1;
for(vector<int>::iterator it = G[k].begin(); it != G[k].end(); ++it)
if(!used[*it])
{
if(lantz[nrl].size() == 1)
tata_lantz[nrl] = k;
descomp(*it);
}
if(G[k].size() == 1 && k != 1){
++nrl; /// frunzaaa :D
lantz[nrl].push_back(0);
}
}
void decomposition()
{
DFSG(1,0);
for(int i = 1; i <= N*N; ++i)
sort(G[i].begin(),G[i].end(),cmp());
++nrl;used = 0;
lantz[1].push_back(0);
descomp(1); -- nrl;
for(int i = 1; i <= nrl; ++i)
for(int j = 0; j <= (1<<((int)log2(lantz[i].size())+ 1)) + 1 ; ++j)
range[i].push_back(0);
for(int i = 1; i <= N; ++i)
for(int j = 1; j <= N; ++j)
valori[(i-1)*N+j] = m[i][j];
}
int line,A,B,answer;
class SegmentTree{
public:
void Build(int li,int lf,int pz)
{
if(li == lf)
{
range[line][pz] = valori[lantz[line][li]];
return;
}
int m = li + ((lf-li)>>1);
Build(li,m,pz<<1);
Build(m+1,lf,(pz<<1)|1);
range[line][pz] = min(range[line][pz<<1],range[line][(pz<<1)|1]);
}
void Querry(int li,int lf,int pz)
{
if(A <= li && lf <= B)
{
answer = min(answer,range[line][pz]);
return;
}
int m = li+((lf-li)>>1);
if(A <= m) Querry(li,m,pz<<1);
if(m < B) Querry(m+1,lf,(pz<<1)|1);
}
void Make()
{
for(int i = 1; i <= nrl; ++i)
{
line = i;
Build(1,lantz[i].size()-1,1);
}
}
}Aint;
void solve()
{
int x1,y1,x2,y2,x,y;
for(int i = 1; i <= Q; ++i)
{
answer = INF;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x = code(x1,y1);
y = code(x2,y2);
while(care[x] != care[y])
{
if(height[tata_lantz[care[x]]] < height[tata_lantz[care[y]]]) swap(x,y);
line = care[x];
A = 1;
B = pozitie_nod[x];
Aint.Querry(1,lantz[line].size()-1,1);
x = tata_lantz[care[x]];
}
A = pozitie_nod[x];
B = pozitie_nod[y];
line = care[x];
if(A > B)swap(A,B);
Aint.Querry(1,lantz[line].size()-1,1);
printf("%d\n",answer);
}
}
int main()
{
freopen("matrice2.in","r",stdin);
freopen("matrice2.out","w",stdout);
read();
kruskal();
decomposition();
Aint.Make();
solve();
return 0;
}