#include <iostream>
#include <fstream>
#include <assert.h>
inline int max(int a, int b) { return ((a) > (b)) ? (a) : (b); }
inline int left_child(int k) { return k * 2; }
inline int right_child(int k) { return k * 2 + 1; }
inline int father(int k) { return k / 2; }
const char IN[] = "arbint.in", OUT[] = "arbint.out";
const int NMAX = 100001;
using namespace std;
//T o sa contina fie valoarea (daca e frunza)
//sau maximul pe intervalul pe care il defineste
//intervalele sunt definite ca submultimi ale vectorului, nu ca valori!
//T[1] = max(v[1], v[2], ..., v[n])
//T[2] = max(v[1]...v[n/2])
//T[3]) = max(v[n/2]+1, ... , v[n]) etc.
int T[NMAX * 4];
int N;
int M;
int maxim;
int OP;
void update(int node, int low, int high, int pos, int val)
{
if (low == high) {
T[node] = val;
return;
}
int m = low + (high - low) / 2;
if (pos <= m) update(left_child(node), low, m, pos, val);
else update(right_child(node), m + 1, high, pos, val);
T[node] = max(T[left_child(node)], T[right_child(node)]);
}
void query(int node, int low, int high, int a, int b)
{
if (a <= low && high <= b) {
maxim = max(maxim, T[node]);
return;
}
int m = low + (high - low) / 2;
//daca nu e un interval fix (gen (8,9)) o sa trebuiasca
//sa caute si in stanga si in dreapta -> (8,8) si (9,9)
if (a <= m) query(left_child(node), low, m, a, b);
if (b > m) query(right_child(node), m + 1, high, a, b);
}
inline void read_data()
{
assert(freopen(IN, "r", stdin));
assert(freopen(OUT, "w", stdout));
assert(scanf("%d %d", &N, &OP));
for (int i = 1; i <= N; ++i) {
int x;
assert(scanf("%d", &x));
update(1, 1, N, i, x);
}
for (int i = 1; i <= OP; ++i) {
int o, a, b;
assert(scanf("%d %d %d", &o, &a, &b));
switch (o) {
case 0:
maxim = -1;
query(1, 1, N, a, b);
printf("%d\n", maxim);
break;
case 1: update(1, 1, N, a, b); break;
}
}
fclose(stdout);
fclose(stdin);
}
int main()
{
read_data();
return 0;
}