#include <algorithm>
#include <iostream>
#include <fstream>
#include <climits>
#include <vector>
#include <stack>
#include <cmath>
// #include <bits/stdc++.h>
#define in cin
#define out cout
using namespace std;
struct nod{
int val; // ce valoare tine
int lant; // in ce lant este
int idx; // in reindexare (da este inspirata de solutia Emei)
int dad; // se explica din nume
int sch; // nr maxim de schimbari pana jos
int nivel; // unde e nodul ca nivel in graf
};
nod v[100001];
vector<int> g[100001]; // adiancentele
int aint[4 * 100001];
int n;
vector<int> l; // l[i] = top-ul lantului i
void build_lanturi(int node){
if(g[ node ].size() == 1 && g[node][0] == v[node].dad){
// cout << node << " e leaf.\n";
v[node].lant = l.size(); // se face lant nou pentru nodul meu
l.push_back(node);
v[node].sch = 0;
return;
}
int maxi = -1, cntmax = 0, care_copil = 0;
for(const int &cop : g[node]){
if(cop == v[node].dad) continue;
v[cop].dad = node;
v[cop].nivel = v[node].nivel + 1;
build_lanturi(cop);
if(v[cop].sch > maxi){
maxi = v[cop].sch;
care_copil = cop;
cntmax = 1;
}else if(v[cop].sch == maxi) cntmax++;
}
// cout << "Am terminat cu " << node << '\n';
// cout << " -- > Continui lantul de la " << care_copil << '\n';
v[node].lant = v[care_copil].lant;
l[ v[node].lant ] = node;
if(cntmax == 1) v[node].sch = maxi;
else v[node].sch = maxi + 1;
}
// acuma trebe sa le reindexed
bool cmp(const int &a, const int &b){
if(v[a].lant != v[b].lant) return v[a].lant < v[b].lant;
else return v[a].nivel < v[b].nivel; // sa fie ala de sus mai in fata
}
vector<int> freaky_sort; // reindex
void reindexare(){
for(int i = 1; i <= n; i++) freaky_sort.push_back(i);
sort(freaky_sort.begin(), freaky_sort.end(), cmp);
// cerr << "freak : ";
// for(const int &x : freaky_sort) cerr << x << " ";
// cerr << '\n';
for(int i = 0; i < n; i++){
v[ freaky_sort[i] ].idx = i + 1;
}
}
// acuma trebe aint
void build(int l, int r, int p){
if(l == r){
aint[p] = v[ freaky_sort[l - 1] ].val;
return;
}
int m = (l + r) / 2;
build(l, m, p * 2);
build(m + 1, r, p * 2 + 1);
aint[p] = max(aint[p * 2], aint[p * 2 + 1]);
}
void update(int l, int r, int p, int poz, int val){
if(l == r){
aint[p] = val;
// v[poz].val = val;
return;
}
int m = (l + r) / 2;
if(poz <= m) update(l, m, p * 2, poz, val);
else update(m + 1, r, p * 2 + 1, poz, val);
aint[p] = max(aint[p * 2], aint[p * 2 + 1]);
}
int query(int l, int r, int p, int l1, int r1){
if(l1 <= l && r <= r1) return aint[p];
int m = (l + r) / 2;
int sol = 0;
if(l1 <= m) sol = max(sol, query(l, m, p * 2, l1, r1));
if(r1 > m) sol = max(sol, query(m + 1, r, p * 2 + 1, l1, r1));
return sol;
}
/*
deci ce skibidi facem de aici este
- am a si b
- tot il aduc pe ala de pe lantul de mai jos mai sus pana is pe acelasi lant
- fac un query sigma pe aint la fiecare
*/
int query_freaky(int a, int b){ // query pentru alea reale, 2 noduri
int sol = 0;
while(v[a].lant != v[b].lant){
int capA = l[ v[a].lant ];
int capB = l[ v[b].lant ];
// cout << "Sunt la a = " << a << " si b = " << b << '\n';
// cout << "----> capa = " << capA << " capb = " << capB << '\n';
if( v[ capA ].nivel < v[ capB ].nivel ){
// osa fur ideea Emei si atunci a e totimpul mai jos
swap(a, b);
swap(capA, capB);
}
sol = max(sol, query(1, n, 1, v[capA].idx, v[a].idx));
// cout << "sol = " << sol << '\n';
a = v[ capA ].dad;
}
// cout << "a = " << a << " b = " << b << '\n';
// si ultimul lant
if(v[a].nivel < v[b].nivel) sol = max(sol, query(1, n, 1, v[a].idx, v[b].idx));
else sol = max(sol, query(1, n, 1, v[b].idx, v[a].idx));
// cout << "sol = " << sol << '\n';
return sol;
}
signed main(){
ios_base::sync_with_stdio(false);
in.tie(NULL);
int q; in >> n >> q;
for(int i = 1; i <= n; i++) in >> v[i].val;
for(int i = 0; i < n - 1; i++){
int a, b; in >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
v[1].dad = -1;
v[1].nivel = 0;
build_lanturi(1);
reindexare();
build(1, n, 1);
for(int _i = 0; _i < q; _i++){
int t, a, b; in >> t >> a >> b;
if(t == 0) update(1, n, 1, v[a].idx, b);
else out << query_freaky(a, b) << '\n';
}
return 0;
}