/*
Am schimbat la linia 225
*/
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#define NMAX 100000 //o suta de mii
using namespace std;
ifstream fin ("heavypath.in");
ofstream fout ("heavypath.out");
int N, M;
int lantGlobal;
int **tree;
int val[NMAX + 1];
int weight[NMAX + 1];
int fiu_mx[NMAX + 1];
int dim[NMAX + 1];
int tataLant[NMAX + 1];
int pozLant[NMAX + 1];
int lantID[NMAX + 1];
int tt[NMAX + 1];
int height[NMAX + 1];
vector <int> G[NMAX + 1];
void precalculareOne(int node, int tata){
/*
weight[] si fiu_mx[]
*/
tt[node] = tata;
weight[node] = 1;
height[node] = height[tata] + 1;
for(int i = 0; i < G[node].size(); i++){
int vec = G[node][i];
if(vec != tata){
precalculareOne(vec, node);
if(weight[vec] > weight[ fiu_mx[node] ]){
fiu_mx[node] = vec;
}
weight[node] += weight[vec];
}
}
}
void precalculareTwo(int node, int tata){
dim[lantGlobal]++;
if(dim[lantGlobal] == 1){
tataLant[lantGlobal] = node;
}
pozLant[node] = dim[lantGlobal];
lantID[node] = lantGlobal;
/*
Daca e frunza, return
altfel, continui in fii
*/
if(weight[node] == 1){
return; //frunza
}
precalculareTwo(fiu_mx[node], node);
for(int i = 0; i < G[node].size(); i++){
int vec = G[node][i];
if(vec != tata && vec != fiu_mx[node]){
lantGlobal++;
precalculareTwo(vec, node);
}
}
}
int IDTree;
int pozUpdate;
int valUpdate;
void updateTree(int node, int left, int right){
if(left == right){
tree[IDTree][node] = valUpdate;
return;
}
int mid = left + (right - left) / 2;
if(pozUpdate <= mid){
updateTree(node * 2, left, mid);
}
else {
updateTree(node * 2 + 1, mid + 1, right);
}
tree[IDTree][node] = max(tree[IDTree][node * 2], tree[IDTree][node * 2 + 1]);
}
void updateTreeUse(int ID, int poz, int val){
IDTree = ID;
pozUpdate = poz;
valUpdate = val;
updateTree(1, 1, dim[ID]);
}
int A, B; //IDTree e deja declarat mai sus
int queryTree(int node, int left, int right){
if(A <= left && right <= B){
return tree[IDTree][node];
}
int mid = left + (right - left) / 2;
int rez1 = -1, rez2 = -1;
if(A <= mid){
rez1 = queryTree(node * 2, left, mid);
}
if(B >= mid + 1){
rez2 = queryTree(node * 2 + 1, mid + 1, right);
}
return max(rez1, rez2);
}
int queryTreeUse(int ID, int st, int dr){
if(st > dr){
swap(st, dr);
}
IDTree = ID;
A = st;
B = dr;
return queryTree(1, 1, dim[ID]);
}
int query(int x, int y){
/*
o functie de divide et impera
mai intai tratez cazul de baza, cand se afla in acelasi lant
*/
if(lantID[x] == lantID[y]){
return queryTreeUse(lantID[x], pozLant[x], pozLant[y]);
}
if(height[ lantID[x] ] > height[ lantID[y] ]){
swap(x, y);
}
//presupun ca lantul lui y incepe mai jos de lantul lui x
int rez1 = query(y, tataLant[ lantID[y] ]);
int rez2 = query(x, tt[ tataLant[ lantID[y] ] ]);
return max(rez1, rez2);
}
void alocareDinamicaTree(){
tree = (int **) malloc( (lantGlobal + 1) * sizeof(int *) );
for(int i = 1; i <= lantGlobal; i++){
tree[i] = (int *) malloc( (4 * dim[i] + 1) * sizeof(int) );
for(int j = 0; j <= 4 * dim[i]; j++){
tree[i][j] = 0;
}
}
}
void buildTree(){
for(int i = 1; i <= N; i++){
updateTreeUse(lantID[i], pozLant[i], val[i]);
}
}
int main()
{
fin >> N >> M;
for(int i = 1; i <= N; i++){
fin >> val[i];
}
for(int i = 1; i <= N - 1; i++){
int x, y;
fin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
precalculareOne(1, 1);
lantGlobal = 1;
precalculareTwo(1, 1);
alocareDinamicaTree();
buildTree();
/*
for(int i = 1; i <= N; i++){
printf("%d apartine lantului %d\n", i, lantID[i]);
}
printf("\n");
for(int i = 1; i <= lantGlobal; i++){
printf("dim[ %d ] = %d\n", i, dim[i]);
}
printf("\n");
for(int i = 1; i <= N; i++){
printf("pozLant[ %d ] = %d\n", i, pozLant[i]);
}
printf("\n");
*/
for(int i = 1; i <= M; i++){
int tip, x, y;
fin >> tip >> x >> y;
if(tip == 0){
updateTreeUse(lantID[x], pozLant[x], y);
}
else {
fout << query(x, y) << "\n";
}
}
return 0;
}