#include <fstream>
#include <vector>
#include <algorithm>
#define NM 100010
using namespace std;
ifstream f("heavypath.in");
ofstream g("heavypath.out");
int N, M;
int NoChains;
int ANS;
int DFLevel[NM], Weight[NM], Value[NM];
bool Visited[NM];
int Chain[NM], ChainFather[NM], ChainLevel[NM], ChainDimension[NM];
int ChainPosition[NM], ChainBegin[NM];
int AI[8*NM];
vector<int> ChainVertex[NM];
vector<int> G[NM];
int DF (int Node, int Father)
{
Visited[Node]=1;
Weight[Node]=1;
bool Leaf=1;
int Heaviest=0;
for (vector<int>::iterator it=G[Node].begin(); it!=G[Node].end(); ++it)
if (Visited[*it]==0)
{
Leaf=0;
DFLevel[*it]=DFLevel[Node]+1;
DF(*it, Node);
Weight[Node]+=Weight[*it];
if (Weight[*it]>Weight[Heaviest] || Heaviest==0)
Heaviest=*it; // determin fiul cu cele mai multe noduri in subarbore
}
if (Leaf==1) // nodul curent e frunza, care determina un nou lant
{
Chain[Node]=++NoChains;
ChainDimension[NoChains]++;
ChainVertex[NoChains].push_back(Node);
}
else // il adaug lantului de care apartine nodul Heaviest
{
Chain[Node]=Chain[Heaviest];
ChainDimension[Chain[Heaviest]]++;
ChainVertex[Chain[Heaviest]].push_back(Node);
for (vector<int>::iterator it=G[Node].begin(); it!=G[Node].end(); ++it)
if (*it!=Father && *it!=Heaviest)
{
ChainFather[Chain[*it]]=Node; // setez pentru fiecare lant din subarborele nodului, care este tatal si nivelul acestui lant
ChainLevel[Chain[*it]]=DFLevel[Node];
}
}
}
void Build (int Node, int S, int D, int Delay, int WChain)
{
if (S>=D)
{
AI[Node+Delay]=Value[ChainVertex[WChain][S-1]];
return;
}
int M=(S+D)/2;
Build(2*Node, S, M, Delay, WChain);
Build(2*Node+1, M+1, D, Delay, WChain);
AI[Node+Delay]=max(AI[2*Node+Delay], AI[2*Node+1+Delay]);
}
void PathDecomposition ()
{
DFLevel[1]=1;
DF(1, 0);
int i, j;
vector<int>::iterator it;
for (i=1; i<=NoChains; i++)
{
reverse(ChainVertex[i].begin(), ChainVertex[i].end()); // ca sa am nodurile in ordine crescatoare a adancimii
for (it=ChainVertex[i].begin(), j=1; it!=ChainVertex[i].end(); ++it, ++j)
ChainPosition[*it]=j;
ChainBegin[i]=ChainBegin[i-1]+4*ChainDimension[i-1]; // calculez decalaju in AINT
Build(1, 1, ChainDimension[i], ChainBegin[i], i);
}
}
void Update (int Node, int S, int D, int Delay, int Pos, int NewVal)
{
if (S>=D)
{
AI[Node+Delay]=NewVal;
return;
}
int M=(S+D)/2;
if (Pos<=M)
Update(2*Node, S, M, Delay, Pos, NewVal);
else
Update(2*Node+1, M+1, D, Delay, Pos, NewVal);
AI[Node+Delay]=max(AI[2*Node+Delay], AI[2*Node+1+Delay]);
}
void Query (int Node, int S, int D, int Delay, int L, int R)
{
if (L<=S && D<=R)
{
ANS=max(ANS, AI[Node+Delay]);
return;
}
int M=(S+D)/2;
if (L<=M)
Query(2*Node, S, M, Delay, L, R);
if (R>M)
Query(2*Node+1, M+1, D, Delay, L, R);
}
int main ()
{
f >> N >> M;
for (int i=1; i<=N; i++)
f >> Value[i];
for (int i=1; i<N; i++)
{
int a, b;
f >> a >> b;
G[a].push_back(b);
G[b].push_back(a);
}
PathDecomposition();
for (int i=1; i<=M; i++)
{
int t;
f >> t;
if (t==0)
{
int node, value;
f >> node >> value;
// updatez in arborele de intervale corespunzator nodului, noua sa valoare
Update(1, 1, ChainDimension[Chain[node]], ChainBegin[Chain[node]], ChainPosition[node], value);
}
else
{
int a, b;
f >> a >> b;
ANS=0;
while (1)
{
if (Chain[a]==Chain[b])
{
if (DFLevel[a]>DFLevel[b])
swap(a, b);
// a si b sunt din acelasi lant, deci pot cauta raspunsul in aint-ul corespunzator acelui lant
Query(1, 1, ChainDimension[Chain[a]], ChainBegin[Chain[a]], ChainPosition[a], ChainPosition[b]);
break;
}
if (ChainLevel[Chain[a]]<ChainLevel[Chain[b]])
swap(a, b);
// a si b sunt din lanturi diferite, il iau pe a cel mai de jos
// il duc in "varful" lantului (adica fac query pe aint-ul corespunzator lantului, intre "varf" si a
Query(1, 1, ChainDimension[Chain[a]], ChainBegin[Chain[a]], 1, ChainPosition[a]);
// apoi ma duc in tatale lantului curent
a=ChainFather[Chain[a]];
}
g << ANS << '\n';
}
}
f.close();
g.close();
return 0;
}