#include <bits/stdc++.h>
using namespace std;
const int NMAX = 1e5 + 2;
const int INF = (1 << 30);
int val[NMAX+2];
struct Atom
{
int cnt, val;
Atom(int cnt = 0, int val = -INF) : cnt(cnt), val(val) {}
Atom operator + (const Atom& o) const
{
return Atom(cnt + o.cnt, max(val, o.val));
}
};
struct SegmentTree
{
vector<Atom> aint;
int N;
SegmentTree(int n = 1) : aint(vector<Atom>(4*n + 1, Atom())), N(n) {}
void update(int nod, int st, int dr, int pos, int val)
{
if( pos < st || dr < pos ) return ;
if( st == dr ) {
aint[nod] = Atom(1, val);
return ;
}
int m = (st + dr) / 2;
update(2*nod, st, m, pos, val);
update(2*nod + 1, m + 1, dr, pos, val);
aint[nod] = (aint[2*nod] + aint[2*nod + 1]);
}
void update(int pos, int val)
{
assert( 1 <= pos && pos <= N );
update(1, 1,N, pos, val);
}
int query(int nod, int st, int dr, int x, int y)
{
if( y < st || dr < x ) return -INF;
if( x <= st && dr <= y ) return aint[nod].val;
int m = (st + dr) / 2;
return max( query(2*nod, st, m, x, y), query(2*nod + 1, m + 1, dr, x, y) );
}
int query(int x, int y)
{
return query(1, 1,N, x, y);
}
};
namespace HLD
{
const int SZ = 1e5 + 2;
/// one time use only, reset by hand if necessary
int N;
int root[SZ];
int parent[SZ], where[SZ], weight[SZ], depth[SZ];
int nr_lant = 0, lg[SZ];
vector<vector<int>> nodes;
vector<SegmentTree> aint;
void dfs(vector<int>(& G)[SZ], int nod, int tata = -1)
{
weight[nod] = 1;
int greu = 0;
for( int x : G[nod] ) {
if( x == tata ) continue;
parent[x] = nod;
depth[x] = depth[nod] + 1;
dfs(G, x, nod);
weight[nod] += weight[x];
if( !greu || weight[greu] < weight[x] )
greu = x;
}
if( greu == 0 ) {
where[nod] = nr_lant;
lg[ where[nod] ] = 1;
root[nod] = nod;
aint.push_back(SegmentTree());
++nr_lant;
}
else {
where[nod] = where[greu];
lg[ where[nod] ] ++;
root[nod] = root[greu];
}
for( int x : G[nod] ) {
if( x == tata ) continue;
if( x != greu )
aint[ where[x] ] = SegmentTree( lg[ where[x] ] );
}
if( tata == -1 )
aint[ where[nod] ] = SegmentTree( lg[ where[nod] ] );
}
void recalc_root()
{
for( int i = 1; i <= N; ++i ) {
if( root[i] == i ) {
int old_root = i;
int new_root = old_root;
while( root[ parent[new_root] ] == old_root )
new_root = parent[new_root];
for( int x = old_root; root[x] == old_root; x = parent[x] )
root[x] = new_root;
}
}
}
void make_hld(vector<int>(& G)[SZ], int input_size)
{
N = input_size;
depth[1] = 1;
parent[1] = 0;
weight[0] = 0;
dfs(G, 1);
recalc_root();
}
inline int treePos(int nod)
{
return depth[nod] - depth[ root[nod] ] + 1;
}
void update(int nod, int val)
{
int pos = treePos(nod);
assert(pos <= aint[ where[nod] ].N);
aint[ where[nod] ].update(pos, val);
}
int query(int x, int y)
{
assert( 1 <= root[x] && root[x] <= N );
assert( 1 <= root[y] && root[y] <= N );
int ret = -INF;
while( root[x] != root[y] ) {
if( depth[x] < depth[y] ) swap(x, y);
ret = max( ret, aint[ where[x] ].query( 1, treePos(x) ) );
x = parent[ root[x] ];
}
if( depth[x] > depth[y] ) swap(x, y);
ret = max( ret, aint[ where[x] ].query( treePos(x), treePos(y) ) );
assert( 0 <= ret && ret <= 2000000000 );
return ret;
}
}
ifstream in("heavypath.in");
ofstream out("heavypath.out");
vector<int> G[NMAX];
int N, Q;
int main()
{
in >> N >> Q;
for( int i = 1; i <= N; ++i ) in >> val[i];
for( int i = 1; i < N; ++i ) {
int x,y;
in >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
HLD::make_hld(G, N);
for( int i = 1; i <= N; ++i )
HLD::update(i, val[i]);
for( int i = 1; i <= Q; ++i ) {
int t,x,y;
in >> t >> x >> y;
if( t == 0 ) {
HLD::update(x, y);
}
else
out << HLD::query(x, y) << '\n';
}
return 0;
}