#include<cstdio>
#include<vector>
#include<bitset>
#include<deque>
#include<algorithm>
using namespace std;
const int NMAX = 100000+5;
const int MMAX = 100000+5;
const int VMAX = 2000000000+5;
void Read(),Make_Paths(),Solve_Queries();
// Functii AInt
void Build(int,int,int,int);
void Update(int,int,int,int,int,int);
int Query(int,int,int,int,int,int);
int N,M,NrPaths;
int Val[NMAX]; // Val[i] = valoarea nodului i
int Dad[NMAX]; // Dad[i] = tatal nodului i
int Niv[NMAX]; // Niv[i] = nivelul nodului i
int Weight[NMAX]; // Weight[i] = greutatea subarborelui nodului i
int E[NMAX]; // E[i] = drumul pe care se afla nodul i
int Poz[NMAX]; // Poz[i] = pozitia in drumul sau pentru al i-lea nod
int PSize[NMAX]; // PSize[i] = dimensiunea celui de-al i-lea drum
vector<int> V[NMAX]; // arborele prin liste de adiacenta
vector<int> Path[NMAX]; // Path[i] = nodurile ce fac parte din al i-lea drum
bitset<NMAX> Viz;
int *AInt[NMAX];
void Read()
{
int i,x,y;
freopen("heavypath.in","r",stdin);
freopen("heavypath.out","w",stdout);
scanf("%d%d",&N,&M);
for(i=1; i<=N; i++)
scanf("%d",&Val[i]);
for(i=1; i<=N-1; i++)
{
scanf("%d%d",&x,&y);
V[x].push_back(y);
V[y].push_back(x);
}
}
inline bool cmp_w(int x,int y)
{
return Weight[x]>Weight[y];
}
void DFS(int nod)
{
vector<int>::iterator it;
int aux=0;
Viz[nod]=1;
Weight[nod]=1;
for(it=V[nod].begin(); it!=V[nod].end(); it++)
{
if(Viz[*it]) continue;
Niv[*it]=Niv[nod]+1;
DFS(*it);
Dad[*it]=nod;
Weight[nod]+=Weight[*it]; // Adunam greutatile fiilor la greutatea tatalui
if(Weight[*it]>Weight[aux]) aux=*it; // Cautam cel mai greu fiu
}
if(Weight[nod]==1) // Daca nodul curent e o frunza...
{
NrPaths++; // ... incepem un drum nou
Poz[nod]=0;
Path[NrPaths].push_back(nod);
E[nod]=NrPaths;
PSize[NrPaths]++;
return;
}
// Adaugam nodul curent la drumul sau
Poz[nod]=Poz[Path[E[aux]].back()]+1;
Path[E[aux]].push_back(nod);
E[nod]=E[aux];
PSize[E[aux]]++;
}
void Make_Paths()
{
int i;
Niv[1]=1;
DFS(1);
for(i=1; i<=NrPaths; i++)
{
// Drumul l-am facut de la frunza la radacina, asa ca il rotesc
reverse(Path[i].begin(),Path[i].end());
// Construiesc AInt pentru drumul curent
AInt[i]=new int[4*PSize[i]];
Build(1,1,PSize[i],i);
}
// Am rotit drumul, asa ca "rotesc" si pozitiile in drum
for(i=1; i<=N; i++)
Poz[i]=PSize[E[i]]-Poz[i];
}
int Solve(int x,int y)
{
int ans=-1;
while(1)
{
// Daca doua noduri sunt pe acelasi drum, scot solutia
if(E[x]==E[y]) return max(ans,Query(1,1,PSize[E[x]],E[x],min(Poz[x],Poz[y]),max(Poz[x],Poz[y])));
// Extrag solutia de pe o bucata de drum, apoi urc pe un drum superior
if(Niv[Path[E[x]][0]] < Niv[Path[E[y]][0]]) swap(x,y);
ans=max(ans,Query(1,1,PSize[E[x]],E[x],1,Poz[x]));
x=Dad[Path[E[x]][0]];
}
}
void Do_Queries()
{
int t,x,y;
for(; M; --M)
{
scanf("%d%d%d",&t,&x,&y);
if(t==0) Update(1,1,PSize[E[x]],Poz[x],y,E[x]);
else printf("%d\n",Solve(x,y));
}
}
int main()
{
Read();
Make_Paths();
Do_Queries();
return 0;
}
void Build(int nod,int lo,int hi,int path)
{
if(lo==hi)
{
AInt[path][nod]=Val[Path[path][lo-1]];
return;
}
int mi=(lo+hi)/2;
Build(2*nod,lo,mi,path);
Build(2*nod+1,mi+1,hi,path);
AInt[path][nod]=max(AInt[path][2*nod],AInt[path][2*nod+1]);
}
void Update(int nod,int lo,int hi,int poz,int val,int path)
{
if(lo==hi)
{
AInt[path][nod]=val;
return;
}
int mi=(lo+hi)/2;
if(poz<=mi) Update(2*nod,lo,mi,poz,val,path);
else Update(2*nod+1,mi+1,hi,poz,val,path);
AInt[path][nod]=max(AInt[path][2*nod],AInt[path][2*nod+1]);
}
int Query(int nod,int lo,int hi,int path,int A,int B)
{
if(A<=lo && hi<=B) return AInt[path][nod];
if(B<lo || hi<A) return -1;
int mi=(lo+hi)/2,st,dr;
st=Query(2*nod,lo,mi,path,A,B);
dr=Query(2*nod+1,mi+1,hi,path,A,B);
return max(st,dr);
}