#include <iostream>
#include <fstream>
#include <climits>
#define NMAX 100000
using namespace std;
ifstream fin("arbint.in");
ofstream fout("arbint.out");
int a[NMAX];
int ST[4 * NMAX];
int maxim(int x, int y) {
return x > y ? x : y;
}
void build(int node, int l, int r) {
if (l == r) {
ST[node] = a[l];
}
else if (l < r) {
int middle = (l + r) / 2;
build(2 * node + 1, l, middle);
build(2 * node + 2, middle + 1, r);
ST[node] = maxim(ST[2 * node + 1], ST[2 * node + 2]);
}
}
void update(int arrayIndex, int newValue, int node, int l, int r) {
if (l == r) {
a[arrayIndex] = ST[node] = newValue;
} else {
int middle = (l + r) / 2;
if (arrayIndex <= middle) {
update(arrayIndex, newValue, 2 * node + 1, l, middle);
} else {
update(arrayIndex, newValue, 2 * node + 2, middle + 1, r);
}
ST[node] = maxim(ST[2 * node + 1], ST[2 * node + 2]);
}
}
int query(int node, int queryLeft, int queryRight, int l, int r) {
// if [l, r] included in [queryLeft, queryRight], return ST[node]
if (l >= queryLeft && queryRight >= r) {
return ST[node];
}
// if intersection is null, return -INF
if (queryLeft > r || queryRight < l) {
return INT_MIN;
}
// if intersection is partial, return the maximum of the two queries
int middle = (l + r) / 2;
return maxim(query(2 * node + 1, queryLeft, queryRight, l, middle),
query(2 * node + 2, queryLeft, queryRight, middle + 1, r));
}
int main()
{
int n, m;
fin >> n >> m;
for (int i = 0; i < n; ++i) {
fin >> a[i];
}
// Construim arborele
build(0, 0, n - 1);
while (m--) {
int op, x, y;
fin >> op >> x >> y;
if (op == 1) {
update(x - 1, y, 0, 0, n - 1);
} else {
fout << query(0, x - 1, y - 1, 0, n - 1) << '\n';
}
}
return 0;
}