#pragma GCC optimize("Ofast")
#define NDEBUG
#include <bits/stdc++.h>
using namespace std;
#ifndef LOCAL
ifstream in("heavypath.in");
ofstream out("heavypath.out");
#define cin in
#define cout out
#endif // LOCAL
const int NMAX = 1e5 + 8;
#include <vector>
#include <cstdint>
#include <cassert>
#include <string>
namespace ja::AdvancedDataStructures::SegmentTree {
#ifndef jassert
#define jassert(expr, msg, ...) assert(expr && msg)
#endif // jassert
#ifndef jlog
#define jlog(...) ((void)0)
#endif // jlog
template < typename DATA, typename OPS >
class PointUpdateRangeQuery {
private:
std::vector<DATA> data;
int32_t offset;
OPS op{};
void _set(int32_t poz, DATA val) {
data[poz + offset] = val;
for(poz = (poz + offset) / 2; poz > 0; poz /= 2)
data[poz] = op.sum(data[2 * poz], data[2 * poz + 1]);
}
DATA _get(int32_t poz) {
return data[poz + offset];
}
void _update(int32_t poz, DATA val) {
data[poz + offset] = op.sum(data[poz + offset], val);
for(poz = (poz + offset) / 2; poz > 0; poz /= 2)
data[poz] = op.sum(data[2 * poz], data[2 * poz + 1]);
}
DATA _query(int32_t l, int32_t r) {
DATA l_ret = op.zero(), r_ret = op.zero();
l = l + offset; r = r + offset;
while(l < r) {
if(l & 1) { l_ret = op.sum(l_ret, data[l]); l += 1; } // l is a right child, include it
if(r & 1) { r -= 1; r_ret = op.sum(data[r], r_ret); } // r is a right child, exclude it
l /= 2; r /= 2;
}
return op.sum(l_ret, r_ret);
}
public:
PointUpdateRangeQuery(int32_t n) {
offset = 1;
while(offset < n) offset *= 2;
data.resize(2 * offset, op.zero());
};
void set(int32_t position, DATA value) {
jassert(0 <= position && position < offset, "position is out of bounds", position, offset);
_set(position, value);
}
DATA get(int32_t position) {
jassert(0 <= position && position < offset, "position is out of bounds", position, offset);
return _get(position);
}
void update(int32_t position, DATA change) {
jassert(0 <= position && position < offset, "position is out of bounds", position, offset);
_update(position, change);
}
DATA query(int32_t left, int32_t right) {
jassert(0 <= left && left <= right && right < offset, "left and right are not correct", left, right, offset);
return _query(left, right + 1);
}
std::string to_string() {
std::string ret = "[ ";
for(int i = 1; i < data.size(); i++) {
if((i & (i - 1)) == 0) { // i is the first node on its level
if(i != 1) { // doesn't matter for the root
ret = ret + " | ";
}
}
ret = ret + std::to_string(i) + ": " + std::to_string(data[i]);
if(((i + 1) & i) != 0) { // i is not the last node on its level
ret = ret + ", ";
}
}
ret = ret + " ]";
return ret;
}
};
template < typename DATA, typename OPS >
class RangeUpdatePointQuery {
private:
std::vector<DATA> data;
int32_t offset;
OPS op{};
DATA _query(int32_t position) {
DATA ret = op.zero();
for(int i = position + offset; i > 0; i /= 2)
ret = op.sum(ret, data[i]);
return ret;
}
void _right_update(int32_t l, int32_t r, DATA change) { // Update with change on the right
l = l + offset; r = r + offset;
while(l < r) {
if(l & 1) { data[l] = op.sum(data[l], change); l += 1; } // l is a right child, update it
if(r & 1) { r -= 1; data[r] = op.sum(data[r], change); } // r is a right child, update its sibling
l /= 2; r /= 2;
}
}
void _left_update(int32_t l, int32_t r, DATA change) { // Updte with change on the left
l = l + offset; r = r + offset;
while(l < r) {
if(l & 1) { data[l] = op.sum(change, data[l]); l += 1; } // l is a right child, update it
if(r & 1) { r -= 1; data[r] = op.sum(change, data[r]); } // r is a right child, update its sibling
l /= 2; r /= 2;
}
}
public:
RangeUpdatePointQuery(int32_t n) {
offset = 1;
while(offset < n) offset *= 2;
data.resize(2 * offset, op.zero());
}
DATA query(int32_t position) {
jassert(0 <= position && position < offset, "position is out of bounds", position, offset);
return _query(position);
}
void right_update(int32_t left, int32_t right, DATA change) {
jassert(0 <= left && left <= right && right < offset, "left and right are not correct", left, right, offset);
_right_update(left, right + 1, change);
}
void left_update(int32_t left, int32_t right, DATA change) {
jassert(0 <= left && left <= right && right < offset, "left and right are not correct", left, right, offset);
_left_update(left, right + 1, change);
}
void update(int32_t left, int32_t right, DATA change) { // Updates are on the right by default
right_update(left, right, change);
}
std::string to_string() {
std::string ret_data = "[ ";
for(int i = 1; i < data.size(); i++) {
if((i & (i - 1)) == 0) { // i is the first node on its level
if(i != 1) { // doesn't matter for the root
ret_data = ret_data + " | ";
}
}
ret_data = ret_data + std::to_string(i) + ": " + std::to_string(data[i]);
if(((i + 1) & i) != 0) { // i is not the last node on its level
ret_data = ret_data + ", ";
}
}
ret_data = ret_data + " ]";
std::string ret_value = "[ ";
for(int i = 0; i < offset; i++) {
if(i != 0) ret_value += ", ";
ret_value += std::to_string(query(i));
}
ret_value += " ]";
return ret_data + " -> " + ret_value;
}
};
};
struct st_ops {
inline int zero() const { return -1e9; }
inline int sum(int a, int b) { return a > b ? a : b; }
};
ja::AdvancedDataStructures::SegmentTree::PointUpdateRangeQuery<int, st_ops> st(NMAX);
vector<int> g[NMAX], liniarize;
int vals[NMAX];
int heavy[NMAX], stsz[NMAX], depth[NMAX], immpar[NMAX];
int inv_liniarize[NMAX], chain_head[NMAX];
void dfs(int node, int parent) {
// cerr << "DFS " << node << " " << parent << endl;
immpar[node] = parent;
stsz[node] = 1; depth[node] = depth[parent] + 1;
heavy[node] = -1;
for(auto vec : g[node]) {
if(vec == parent) continue;
dfs(vec, node);
if(heavy[node] == -1 || stsz[heavy[node]] < stsz[vec]) {
heavy[node] = vec;
}
}
}
void lin(int node, int head) {
// cerr << "LIN " << node << " " << head << endl;
chain_head[node] = head;
inv_liniarize[node] = liniarize.size();
liniarize.push_back(node);
if(heavy[node] != -1) lin(heavy[node], head);
for(auto vec : g[node]) {
if(vec == immpar[node] || vec == heavy[node]) continue;
lin(vec, vec);
}
}
void update(int node, int value) {
st.set(inv_liniarize[node], value);
}
int query(int a, int b) {
// cerr << "Query " << a << " " << b << endl;
// this_thread::sleep_for(1s);
if(chain_head[a] == chain_head[b]) {
if(depth[a] > depth[b]) swap(a, b);
return st.query(inv_liniarize[a], inv_liniarize[b]);
}
if(depth[chain_head[a]] < depth[chain_head[b]]) swap(a, b);
// cerr << "Continuing " << a << " " << b << endl;
// cerr << "with " << immpar[chain_head[a]] << " " << b << endl;
return std::max(
st.query(inv_liniarize[chain_head[a]], inv_liniarize[a]),
query(immpar[chain_head[a]], b)
);
}
const int root = 1;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m; cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> vals[i];
for(int i = 0; i < n - 1; i++) {
int x, y; cin >> x >> y;
g[x].push_back(y);
g[y].push_back(x);
}
dfs(root, 0); lin(root, root);
for(int i = 1; i <= n; i++) update(i, vals[i]);
for(int i = 0; i < m; i++) {
int t, x, y; cin >> t >> x >> y;
if(t == 0) {
update(x, y);
}
else {
cout << query(x, y) << '\n';
}
}
}