#include<cstdio>
#include<cstring>
#include<vector>
#define N 1<<17
using namespace std;
struct AI{int BST,LO,HI;AI *TA,*FS,*FD;};
AI *ai[N],*Nod[N],*nd;
vector<int> v[N],P[N],p;
int n,q,i,x,y,t,z,V[N],niv[N],w[N],a[N],T[N],na,ST,DR,poz[N],DAD[N],Querry(int,int),QuerryA(AI*,int,int);
void df(int),arbore(AI*,AI*,int,int);
int main()
{
freopen("heavypath.in","r",stdin);
freopen("heavypath.out","w",stdout);
scanf("%d%d",&n,&q);
for(i=1;i<=n;i++)scanf("%d",&V[i]);
for(i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
v[x].push_back(y);v[y].push_back(x);
}
niv[1]=1;df(1);
//am drumurile si constriuesc acum arborii de intervale
for(i=1;i<=na;i++)
{
p.resize(0);
ST=0;DR=-1;
ai[i]=new AI;//initializez radacina arborelui;
for(;P[i].size();){x=P[i].back();P[i].pop_back();p.push_back(x);DR++;poz[x]=DR;}
arbore(0,ai[i],ST,DR);//construiesc arborele de intrvale corespunzator
}
for(;q;q--)
{
scanf("%d%d%d",&t,&x,&y);
if(!t)
{
Nod[x]->BST=y;
for(nd=Nod[x]->TA;nd;nd=nd->TA)nd->BST=max(nd->FS->BST,nd->FD->BST);
continue;
}
printf("%d\n",Querry(x,y));
}
return 0;
}
void df(int nod)
{
//setez greutatea nodului la 1;
w[nod]=1;
//daca nodul este frunza
if(v[nod].size()==1&&nod!=1)
{
//construiesc un nou drum(care de fapt va fi un nou arbore de intervale)
a[nod]=++na;
P[na].push_back(nod);
niv[nod]=niv[T[nod]]+1;
DAD[a[nod]]=T[nod];
return;
}
//daca nodul nu este frunza
for(vector<int>::iterator it=v[nod].begin();it!=v[nod].end();it++)
{
//folosind tatal nodului calculez nivelul nodului
if(*it==T[nod])
{
niv[nod]=niv[*it]+1;
continue;
}
//pentru fii apelez df
T[*it]=nod;
df(*it);
//folosesc greutatea fiilor pentru a calcula greutatea nodului
w[nod]+=w[*it];
}
//incep a doua parcurgere sa determin fiul de greutate maxima
int bst=0;
for(vector<int>::iterator it=v[nod].begin();it!=v[nod].end();it++)
//nu ma interezeaza tatal si fii de greutati mai mici decat cea mai buna de pana acum
if(*it!=T[nod]&&w[*it]>bst)
{
//daca gasesc greutate mai mare updatez si redefinesc drumul la care lipesc nodul curent
bst=w[*it];
a[nod]=a[*it];
}
//pun nodul curent in arborele de intervale potrivit si actulaizez nodul la care se va lipi aceste
P[a[nod]].push_back(nod);
DAD[a[nod]]=T[nod];
}
void arbore(AI *DAD,AI *NOD,int st,int dr)
{
AI *aux;
NOD->LO=st;NOD->HI=dr;NOD->TA=DAD;
if(st==dr){NOD->FS=NOD->FD=0;NOD->BST=V[p[st]];Nod[p[st]]=NOD;return;}
aux=new AI;
arbore(NOD,aux,st,(st+dr)/2);
NOD->FS=aux;
aux=new AI;
arbore(NOD,aux,(st+dr)/2+1,dr);
NOD->FD=aux;
NOD->BST=max(NOD->FS->BST,NOD->FD->BST);
}
int Querry(int x1,int x2)
{
if(a[x1]==a[x2]) return QuerryA(ai[a[x1]],min(poz[x1],poz[x2]),max(poz[x1],poz[x2]));
if(niv[DAD[a[x1]]]<niv[DAD[a[x2]]]){z=x1;x1=x2;x2=z;}
return max(QuerryA(ai[a[x1]],0,poz[x1]),Querry(DAD[a[x1]],x2));
}
int QuerryA(AI *NOD,int lo,int hi)
{
if(lo>NOD->HI||hi<NOD->LO)return 0;
if(lo<=NOD->LO&&NOD->HI<=hi)return NOD->BST;
return max(QuerryA(NOD->FS,lo,hi),QuerryA(NOD->FD,lo,hi));
}