#include <stdio.h>
#include <vector>
const int MAXN = 100'000;
const int AINTN = 1 << 17;
const int T_UPDATE = 0;
const int T_QUERY = 1;
FILE *fin, *fout;
std::vector<int> g[MAXN];
int n, q, v[MAXN];
int nextp2(int n) {
while(n & (n - 1)) {
n += n & -n;
}
return n;
}
int max(int a, int b) {
return a > b ? a : b;
}
struct SegmentTree {
int aint[AINTN << 1], n;
void init(int n) {
this->n = nextp2(n);
}
void update(int poz, int val) {
aint[poz += n] = val;
for(poz >>= 1; poz > 0; poz >>= 1) {
aint[poz] = max(aint[2 * poz], aint[2 * poz + 1]);
}
}
int query(int st, int dr) {
int rez;
st += n;
dr += n;
rez = 0;
while(st <= dr) {
if(st & 1) {
rez = max(rez, aint[st++]);
}
st >>= 1;
if(!(dr & 1)) {
rez = max(rez, aint[dr--]);
}
dr >>= 1;
}
return rez;
}
} aint;
struct HeavyLightDecomp {
int poz[MAXN], path_start[MAXN], subtree_size[MAXN], current_poz;
int heavy_fiu[MAXN], parent[MAXN], depth[MAXN];
SegmentTree aint;
void dfs(int nod) {
int i, fiu, max_size;
max_size = 0;
heavy_fiu[nod] = -1;
subtree_size[nod] = 1;
for(i = 0; i < (int)g[nod].size(); i++) {
fiu = g[nod][i];
if(fiu != parent[nod]) {
depth[fiu] = 1 + depth[nod];
parent[fiu] = nod;
dfs(fiu);
subtree_size[nod] += subtree_size[fiu];
if(subtree_size[fiu] > max_size) {
max_size = subtree_size[fiu];
heavy_fiu[nod] = fiu;
}
}
}
}
void decomposeTree(int nod) {
int i, fiu;
if(heavy_fiu[nod] != -1) {
poz[heavy_fiu[nod]] = current_poz++;
path_start[heavy_fiu[nod]] = path_start[nod];
decomposeTree(heavy_fiu[nod]);
}
for(i = 0; i < (int)g[nod].size(); i++) {
fiu = g[nod][i];
if(fiu != heavy_fiu[nod] && parent[nod] != fiu) {
poz[fiu] = current_poz++;
path_start[fiu] = fiu;
decomposeTree(fiu);
}
}
}
void init() {
int i;
depth[0] = 0;
parent[0] = -1;
dfs(0);
path_start[0] = poz[0] = 0;
current_poz = 1;
decomposeTree(0);
aint.init(n);
for(i = 0; i < n; i++) {
aint.update(poz[i], v[i]);
}
}
void update(int nod, int val) {
aint.update(poz[nod], val);
}
int query(int u, int v) {
int answer = 0;
// Cat timp drumurile pana la radacina nu s-au intalnit
while(path_start[u] != path_start[v]) {
// Mai intai operam pe cel cu inceputul lantului mai jos
if(depth[path_start[u]] > depth[path_start[v]]) {
std::swap(u, v);
}
answer = max(answer, aint.query(poz[path_start[v]], poz[v]));
v = parent[path_start[v]];
}
// Acum ei doi sunt pe acelasi lant deci cel de mai sus e LCA
if(depth[u] > depth[v]) {
std::swap(u, v);
}
answer = max(answer, aint.query(poz[u], poz[v]));
return answer;
}
} hld;
void openFiles() {
fin = fopen("heavypath.in", "r");
fout = fopen("heavypath.out", "w");
}
void readData() {
int i, x, y;
fscanf(fin, "%d%d", &n, &q);
for(i = 0; i < n; i++) {
fscanf(fin, "%d", &v[i]);
}
for(i = 1; i < n; i++) {
fscanf(fin, "%d%d", &x, &y);
g[--x].push_back(--y);
g[y].push_back(x);
}
}
void processOps() {
int type, x, y;
while(q--) {
fscanf(fin, "%d%d%d", &type, &x, &y);
x--;
y--;
if(type == T_UPDATE) {
hld.update(x, y);
} else { // T_QUERY
fprintf(fout, "%d\n", hld.query(x, y));
}
}
}
void closeFiles() {
fclose(fin);
fclose(fout);
}
int main() {
openFiles();
readData();
hld.init();
processOps();
closeFiles();
return 0;
}