#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
const int NMAX = 100505;
const int LMAX = (1LL << 18);
const int HMAX = 19;
const int INF = 0x3f3f3f3f;
int n, m, val[NMAX];
vector<int> G[NMAX];
// Tree related info
int lvl[NMAX];
bool mark[NMAX];
int noChildren[NMAX], par[NMAX];
// chains related structs
int whatChain[NMAX], whatPos[NMAX], parentPathSplit[NMAX];
vector<int> Paths[NMAX], PathsArb[NMAX];
int noChains;
void read() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &val[i]);
}
int x, y;
for (int i = 1; i < n; i++) {
scanf("%d%d", &x, &y);
G[x].push_back(y); G[y].push_back(x);
}
}
void dfs(int node) {
mark[node] = true;
noChildren[node] = 1;
for (auto x: G[node])
if (!mark[x]) {
lvl[x] = lvl[node] + 1;
par[x] = node;
dfs(x);
noChildren[node] += noChildren[x];
}
}
void addToLatestChain(int node) {
Paths[noChains - 1].push_back(node);
whatChain[node] = noChains - 1;
whatPos[node] = Paths[noChains - 1].size() - 1;
}
void dfs2(int node) {
if (noChildren[node] == 1) return ;
int bestChild = -1, bestChildValue = 0;
for (auto x: G[node]) {
if (par[x] == node && noChildren[x] > bestChildValue) {
bestChildValue = noChildren[x];
bestChild = x;
}
}
addToLatestChain(bestChild);
dfs2(bestChild);
for (auto x: G[node]) {
if (par[x] == node && x != bestChild) {
noChains++;
addToLatestChain(x);
parentPathSplit[noChains - 1] = node;
dfs2(x);
}
}
}
void cstrSegmentTree(vector<int>& path, vector<int>& arb, int left, int right, int node) {
if (left == right) {
arb[node] = val[path[left]];
return ;
}
int mid = (left + right) / 2;
cstrSegmentTree(path, arb, left, mid, node * 2);
cstrSegmentTree(path, arb, mid + 1, right, node * 2 + 1);
arb[node] = max(arb[node * 2], arb[node * 2 + 1]);
}
void constructChains() {
lvl[0] = -1;
dfs(1);
memset(mark, false, sizeof(mark));
noChains++;
addToLatestChain(1);
dfs2(1);
// cstr segment trees
for (size_t i = 0; i < noChains; i++) {
// infer size of the segment tree for currrent path
int size = Paths[i].size(), step;
for (step = 1; step <= size; step <<= 1);
PathsArb[i].reserve(step * 2 + 1);
cstrSegmentTree(Paths[i], PathsArb[i], 0, size - 1, 1);
}
}
int getLca(int x, int y) {
while (whatChain[x] != whatChain[y]) {
if (lvl[parentPathSplit[whatChain[x]]] > lvl[parentPathSplit[whatChain[y]]]) {
x = parentPathSplit[whatChain[x]];
} else {
y = parentPathSplit[whatChain[y]];
}
}
return lvl[x] < lvl[y] ? x : y;
}
void updateSingle(vector<int>& arb, int left, int right, int pos, int newVal, int node) {
if (left == right) {
arb[node] = newVal;
return ;
}
int mid = (left + right) / 2;
if (pos <= mid)
updateSingle(arb, left, mid, pos, newVal, node * 2);
else
updateSingle(arb, mid + 1, right, pos, newVal, node * 2 + 1);
arb[node] = max(arb[node * 2], arb[node * 2 + 1]);
}
int getMaxInterval(vector<int>& arb, int left, int right, int l, int r, int node) {
if (r < left || right < l) {
return -INF;
}
if (l <= left && right <= r)
return arb[node];
int mid = (left + right) / 2;
return max(
getMaxInterval(arb, left, mid, l, r, node * 2),
getMaxInterval(arb, mid + 1, right, l, r, node * 2 + 1));
}
int getMaxAncestorPath(int x, int y) {
int ret = val[x];
while (x != y) {
int chainIdx = whatChain[y];
if (whatChain[x] != whatChain[y]) {
ret = max(ret, getMaxInterval(
PathsArb[chainIdx], 0, Paths[chainIdx].size() - 1, 0, whatPos[y], 1));
y = parentPathSplit[chainIdx];
} else {
ret = max(ret, getMaxInterval(
PathsArb[chainIdx], 0, Paths[chainIdx].size() - 1, whatPos[x], whatPos[y], 1));
y = x;
}
}
return ret;
}
void solve() {
int type, x, y;
while (m--) {
scanf("%d%d%d", &type, &x, &y);
switch (type) {
case 0: {
val[x] = y;
int chainIdx = whatChain[x];
updateSingle(PathsArb[chainIdx], 0, Paths[chainIdx].size() - 1, whatPos[x], y, 1);
break;
}
case 1: {
int lca = getLca(x, y);
printf("%d\n", max(getMaxAncestorPath(lca, x), getMaxAncestorPath(lca, y)));
break ;
}
}
}
}
int main() {
freopen("heavypath.in", "r", stdin);
freopen("heavypath.out", "w", stdout);
read();
constructChains();
solve();
return 0;
}