// Dungeon
/* Gợi ý giới hạn
1 <= a[i][j] <= 1e6
Sub1: n <= 50, q <= 10
Sub2: n <= 100, q <= 100 (có thể bỏ)
Sub3: n <= 200, q <= 1000
Sub4: n <= 300, q <= 1e5
Sub5: n <= 500, q <= 1e6
*/
// nhánh cận các thứ, cố gắng code các sub tối ưu nhất có thể (tránh code sub nhỏ ăn sub lớn)
// bò: dfs giới hạn lần chạy (vd: 100 lần) nếu chưa đến đích out ra false (có thể sai)
// bò: ngắt dijkstra nếu gặp đích (có thể sai)
// thử code cách cnp song song sử dụng dfs/bfs thay dsu (tle)
#include <bits/stdc++.h>
using namespace std;
struct Query {
int x, y, u, v, id;
};
const int D[4][2] = {{-1,0},{1,0},{0,-1},{0,1}};
const int N = 507;
const int Q = 1e6+7;
int n, q, a[N][N];
Query ques[Q];
int getId(int x, int y) {
return (x-1) * n + y;
}
const int NODE = N*N;
const int EDGE = NODE*2;
struct Edge {
int x, y, w;
Edge() {}
Edge(int _x, int _y, int _w) {
x = _x; y = _y; w = _w;
}
};
int numNode, numEdge, numEvent;
pair<int,int> newQues[Q];
Edge edges[EDGE];
int fa[NODE];
int ans[Q];
set<int> comp[NODE];
vector<int> query[NODE];
int root(int x) {
return fa[x] < 0 ? x : fa[x] = root(fa[x]);
}
void Unite(int u, int v, int w) {
u = root(u);
v = root(v);
if (u == v) return;
if (fa[u] > fa[v]) swap(u,v);
fa[u] += fa[v];
fa[v] = u;
for (int x : comp[v]) {
for (int i = (int)query[x].size()-1; i >= 0; --i) {
int id = query[x][i];
int y = x ^ newQues[id].first ^ newQues[id].second;
if (comp[u].count(y)) {
ans[id] = w;
query[x][i] = query[x].back();
query[x].pop_back();
}
}
}
comp[u].insert(comp[v].begin(),comp[v].end());
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
freopen("matrice2.in", "r", stdin);
freopen("matrice2.out", "w", stdout);
cin >> n >> q;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
cin >> a[i][j];
}
for (int i = 1; i <= q; ++i) {
cin >> ques[i].x >> ques[i].y >> ques[i].u >> ques[i].v;
ques[i].id = i;
}
for (int i = 1; i <= q; ++i) {
newQues[i].first = getId(ques[i].x,ques[i].y);
newQues[i].second = getId(ques[i].u,ques[i].v);
}
numNode = n * n;
numEdge = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (i < n)
edges[numEdge++] = Edge(getId(i,j), getId(i+1,j), min(a[i][j],a[i+1][j]));
if (j < n)
edges[numEdge++] = Edge(getId(i,j), getId(i,j+1), min(a[i][j],a[i][j+1]));
}
sort(edges,edges+numEdge,[&](const Edge &a, const Edge &b) {
return a.w > b.w;
});
memset(fa, -1, (numNode+1) * sizeof(int));
for (int i = 1; i <= numNode; ++i) {
comp[i].insert(i);
}
for (int i = 1; i <= q; ++i) {
query[newQues[i].first].push_back(i);
query[newQues[i].second].push_back(i);
}
for (int i = 0; i < numEdge; ++i)
Unite(edges[i].x, edges[i].y, edges[i].w);
for (int i = 1; i <= q; ++i)
cout << ans[i] << '\n';
return 0;
}