#pragma GCC optimize ("O3")
#pragma GCC optimize ("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef vector<int> vi;

#define FOR(i, a, b) for (int i = (a); i < int(b); i++)
#define FORD(i, a, b) for (int i = (b)-1; i >= int(a); i--)
#define sz(c) int((c).size())
#define all(c) c.begin(), c.end()
#define debug(x) cerr << #x << ": " << x << endl;

const int MAXN = 300005;
const int bucketSize = 765;
const int MAX_BUCKET = 1 + (2*MAXN) / bucketSize;
int a[MAXN];

struct Query {
  int u, v, l, r;
  int idx;
};

vector<Query> vq[MAX_BUCKET];

struct SparseTable {
  vector<vector<int>> ST;
  vector<int> P;
  int N;
  int MAXLOG = 0;

  void build(int n, const vector<int> &V) {
    N = n;
    while ((1 << MAXLOG) <= N)
      ++MAXLOG;
    ST = vector<vector<int>>(N, vector<int>(MAXLOG));
    P = vector<int>(N + 1);
    int LOG = 0;
    for (int i = 1; i < N + 1; ++i) {
      if (1 << (LOG + 1) <= i)
        P[i] = ++LOG;
      else
        P[i] = LOG;
    }
    // Dynamic Sparse table building
    for (int i = 0; i < N; ++i) {
      ST[i][0] = i;
    }
    for (int j = 1; j < MAXLOG; ++j) {
      for (int i = 0; i + (1 << j) - 1 < N; ++i) {
        if (V[ST[i][j - 1]] < V[ST[i + (1 << (j - 1))][j - 1]])
          ST[i][j] = ST[i][j - 1];
        else
          ST[i][j] = ST[i + (1 << (j - 1))][j - 1];
      }
    }
  }

  int query(int l, int r, const vector<int> &V) {
    int LOG = P[r - l + 1];

    if (V[ST[l][LOG]] < V[ST[r - (1 << LOG) + 1][LOG]])
      return ST[l][LOG];
    return ST[r - (1 << LOG) + 1][LOG];
  }
};

struct Tree {
  vector<vector<int>> adj;
  int V, E;
  int LOG;
  int cnt = 0;
  vi tin, tout, inOutTour;
  vector<int> Eulerian_tour, RMQ_reduction, Ocurrence;
  SparseTable S;

  void Eulerian_dfs(int p, int u, int depth) {
    if (Ocurrence[u] == -1)
      Ocurrence[u] = sz(Eulerian_tour);
    tin[u] = cnt++;
    inOutTour.push_back(u);
    Eulerian_tour.push_back(u);
    RMQ_reduction.push_back(depth);
    for (int v : adj[u]) {
      if (v != p) {
        Eulerian_dfs(u, v, depth + 1);
        Eulerian_tour.push_back(u);
        RMQ_reduction.push_back(depth);
      }
    }
    inOutTour.push_back(u);
    tout[u] = cnt++;
  }

  void build(int n) {
    V = n;
    E = n - 1;
    adj.resize(V);
    tin.resize(V);
    tout.resize(V);
    Ocurrence = vector<int>(V, -1);
    for (int i = 0; i < E; ++i) {
      int x, y;
      cin >> x >> y; // we suppose nodes are 0-indexed
      --x, --y;
      adj[x].push_back(y);
      adj[y].push_back(x);
    }
    Eulerian_dfs(0, 0, 0);
    S.build(sz(RMQ_reduction), RMQ_reduction);
  }

  int query(int l, int r) {
    int L = min(Ocurrence[l], Ocurrence[r]);
    int R = max(Ocurrence[l], Ocurrence[r]);
    return Eulerian_tour[S.query(L, R, RMQ_reduction)];
  }
};

int main() {
  ios::sync_with_stdio(0);
  cin.tie(0);
  int n, q;
  cin >> n >> q;
  auto curT = clock();
  for (int i = 0; i < n; ++i) {
    cin >> a[i];
    --a[i];
  }
  Tree t;
  t.build(n);
  if(n == 300000)
    cerr << (double)(clock() - curT) /
      (CLOCKS_PER_SEC);
  vector<int> ans(q, -1337);
  for (int i = 0; i < q; ++i) {
    int u, v, l, r;
    cin >> u >> v >> l >> r;
    --u, --v;
    --l, --r;
    if (t.tin[u] > t.tin[v])
      swap(u, v);
    int qleft = (t.query(u, v) == u ? t.tin[u] : t.tout[u]);
    vq[qleft / bucketSize].push_back({u, v, l, r, i});
  }
  for (int i = 0; i < MAX_BUCKET; ++i) {
    sort(vq[i].begin(), vq[i].end(), [&t](const Query &q1, const Query &q2) {
      return t.tin[q1.v] < t.tin[q2.v];
    });
    int lft = -1;
    int rgt = -1;
    vi oddSum(MAX_BUCKET, 0);
    vi color(n, 0);
    vi status(n, 0);
    for (Query j : vq[i]) {
      int lca = t.query(j.u, j.v);
      int targetLft = t.tin[j.u];
      int targetRgt = t.tin[j.v];
      if (lca != j.u) {
        targetLft = t.tout[j.u];
        color[a[lca]]++;
        if (color[a[lca]] & 1)
          oddSum[a[lca] / bucketSize]++;
        else
          oddSum[a[lca] / bucketSize]--;
      }
      if (lft == -1)
        lft = targetLft, rgt = targetLft - 1;
      int curNode = -1;
      while (lft < targetLft) {
        curNode = t.inOutTour[lft++];
        --status[curNode];
        (status[curNode] & 1 ? ++color[a[curNode]] : --color[a[curNode]]);
        ((color[a[curNode]] & 1) ? oddSum[a[curNode] / bucketSize]++
                                 : oddSum[a[curNode] / bucketSize]--);
      }
      while (lft > targetLft) {
        curNode = t.inOutTour[--lft];
        ++status[curNode];
        (status[curNode] & 1 ? ++color[a[curNode]] : --color[a[curNode]]);
        ((color[a[curNode]] & 1) ? oddSum[a[curNode] / bucketSize]++
                                 : oddSum[a[curNode] / bucketSize]--);
      }
      while (rgt < targetRgt) {
        curNode = t.inOutTour[++rgt];
        ++status[curNode];
        (status[curNode] & 1 ? ++color[a[curNode]] : --color[a[curNode]]);
        ((color[a[curNode]] & 1) ? oddSum[a[curNode] / bucketSize]++
                                 : oddSum[a[curNode] / bucketSize]--);
      }
      while (rgt > targetRgt) {
        curNode = t.inOutTour[rgt--];
        --status[curNode];
        (status[curNode] & 1 ? ++color[a[curNode]] : --color[a[curNode]]);
        ((color[a[curNode]] & 1) ? oddSum[a[curNode] / bucketSize]++
                                 : oddSum[a[curNode] / bucketSize]--);
      }
      int curAns = -2;
      for (int k = 0; curAns == -2 && k < MAX_BUCKET; ++k) {
        int lLimit = bucketSize * k;
        if (lLimit >= n)
          break;
        int rLimit = min(n - 1, bucketSize * (k + 1));

        // totally out
        if (lLimit > j.r || rLimit < j.l)
          continue;
        // totally in
        if (lLimit >= j.l && rLimit <= j.r) {
          // check odd_sum!
          if (oddSum[k]) {
            for (int kk = lLimit; kk <= rLimit; ++kk) {
              if (color[kk] & 1)
                curAns = kk;
            }
          }
        }
        // partially out
        else if (lLimit >= j.l) {
          for (int kk = lLimit; kk <= j.r; ++kk) {
            if (color[kk] & 1)
              curAns = kk;
          }
          // lLimit to j.r
        } else if (rLimit <= j.r) {
          for (int kk = j.l; kk <= rLimit; ++kk) {
            if (color[kk] & 1)
              curAns = kk;
          }
          // j.l to rLimit
        }
        // If the bucket is not outside, not inside and not partial. It means
        // that the other interval is inside??
        else if (j.l >= lLimit && j.r <= rLimit) {
          for (int kk = j.l; kk <= j.r; ++kk) {
            if (color[kk] & 1)
              curAns = kk;
          }
        }
      }
      ans[j.idx] = curAns;
      if (lca != j.u) {
        color[a[lca]]--;
        ((color[a[lca]] & 1) ? oddSum[a[lca] / bucketSize]++
                                 : oddSum[a[lca] / bucketSize]--);
      }
    }
  }
  for (int i : ans) {
    cout << i + 1 << '\n';
  }
}

/*
Things to maintain:
colorToNumber[color] -> number of color appereance
colorToNumberCompressed[color / bucketSize] -> number of odd colors

Mo's
IteratorToNode[iterator] -> Node
NodeToColor[Node] -> Color

NodeToIterator[Node] -> Iterator

 */
