#include <bits/stdc++.h>
#pragma GCC optimize("O3","Ofast")
#pragma GCC target("avx2","bmi","popcnt","lzcnt","bmi2")
using namespace std;
int v[100005], par[100005], arb[100005], level[100005], cnt = 0, curr_comp = 0;
int comp[100005]; //din ce lant heavy face parte
int leader[100005]; //parintele fiecarui lant heavy
int order[100005]; // ce index are DUPA decomp
int reale[100005]; //care nod(din notatia initiala, input) ii corespunde indexului i?
vector<int> adj[100005];
void inradacinez(int u, int p = 0){
par[u] = p;
level[u] = level[p] + 1;
int curr = 1;
for(int nod : adj[u]){
if(nod == p) continue;
inradacinez(nod, u);
curr += arb[nod];
}
arb[u] = curr;
}
void dfs(int nod, bool heavy = 0){
order[nod] = ++cnt;
reale[cnt] = nod;
if(heavy == 0){
comp[nod] = ++curr_comp;
leader[curr_comp] = nod;
}
else
comp[nod] = comp[par[nod]];
int maxarb = -1, which = 0;
for(int copil : adj[nod]){
if(copil == par[nod]) continue;
if(arb[copil] > maxarb){
which = copil;
maxarb = arb[copil];
}
}
if(which != 0)
dfs(which, 1);
for(int copil : adj[nod]){
if(copil == par[nod] || copil == which) continue;
dfs(copil, 0);
}
}
struct segmentTree{
int aint[400005];
void build(int i, int l, int r){
if(l == r){
aint[i] = v[reale[l]]; //actionam asupra celui cu ord[] = l, dar vrem valoarea lui, nu v[l]
return;
}
int mid = (l + r) >> 1;
build(2 * i, l, mid);
build(2 * i + 1, mid + 1, r);
aint[i] = max(aint[2 * i], aint[2 * i + 1]);
}
void update(int i, int l, int r, int poz, int x){
if(l == r && l == poz){
aint[i] = x;
return;
}
if(l > poz || r < poz) return;
int mid = (l + r) >> 1;
update(2 * i, l, mid, poz, x);
update(2 * i + 1, mid + 1, r, poz, x);
aint[i] = max(aint[2 * i], aint[2 * i + 1]);
}
int query(int i, int curl, int curr, int l, int r){
if(curl > r || curr < l) return 0;
if(l <= curl && curr <= r) return aint[i];
int mid = (curl + curr) >> 1;
return max(query(2 * i, curl, mid, l, r),
query(2 * i + 1, mid + 1, curr, l, r));
}
}Tree;
int main()
{
//ios_base::sync_with_stdio(false);
//cin.tie(nullptr);
//cout.tie(nullptr);
ifstream cin("heavypath.in");
ofstream cout("heavypath.out");
int n, q;
cin >> n >> q;
for(int i = 1; i <= n; i++){
cin >> v[i];
}
int a, b;
for(int i = 1; i < n; i++){
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
//o sa fac il inradacinez in 1 si o sa retin parintii in par, also calculez marimea subarborelui in arb si depthul
inradacinez(1);
//acum fac decompostion-ul, imi retin care nod e la rand, din ce lant face parte si parintele lantului
dfs(1);
//fac aintul
Tree.build(1, 1, n);
//in sfarsit, queryuri
int type, node, x, ans;
while(q--){
cin >> type;
if(type == 1){
cin >> node >> x;
Tree.update(1, 1, n, order[node], x);
}
else{
ans = -1;
cin >> a >> b;
for(; leader[comp[a]] != leader[comp[b]]; b = par[leader[comp[b]]]){ // "urc" b tot timpul(totusi le dau swap uneori), cat timp nu le am ridicat la acelasi lant
if(level[leader[comp[a]]] > level[leader[comp[b]]]) //daca lantul lui a este mai jos decat lantul lui b, le schimb
swap(a, b);
ans = max(ans, Tree.query(1, 1, n, order[leader[comp[b]]], order[b])); //mai intai il pun pe cel cu nivel mai sus din arbore, pentru ca are order mai mic
}
//acum a si b sunt pe acelasi lant, mai pun doar ce a ramas
if(level[a] > level[b]) swap(a, b);
ans = max(ans, Tree.query(1, 1, n, order[a], order[b]));
cout << ans << '\n';
}
}
return 0;
}