#include <fstream>
#include <vector>
using namespace std;
ifstream cin("arbint.in");
ofstream cout("arbint.out");
class SegTree {
vector<int> aint;
int capacity;
/*
nod - pozitia din vector pentru nodul [st, dr]
st, dr - intervalul nodului
complexitate: O(N)
*/
void build(int nod, int st, int dr, vector<int> &a) {
if (st == dr) {
aint[nod] = a[st];
} else {
int m = (st + dr) / 2;
build(2 * nod, st, m, a);
build(2 * nod + 1, m + 1, dr, a);
aint[nod] = max(aint[2 * nod], aint[2 * nod + 1]);
}
}
/*
nod - pozitia din vector pentru nodul [st, dr]
st, dr - intervalul nodului
poz, val - A[poz] = val;
complexitate: O(log(N))
*/
void update(int nod, int st, int dr, int poz, int val) {
if (st == dr) { // esti intr o frunza
aint[nod] = val;
} else {
int m = (st + dr) / 2;
if (poz <= m) { // este in subarborele stang
update(2 * nod, st, m, poz, val);
} else {
update(2 * nod + 1, m + 1, dr, poz, val);
}
aint[nod] = max(aint[2 * nod], aint[2 * nod + 1]);
}
}
/*
nod - pozitia din vector pentru nodul [st, dr]
st, dr - intervalul nodului
qst, qdr - intervalul de query
complexitate: O(log(N))
*/
int query(int nod, int st, int dr, int qst, int qdr) {
if (st >= qst && dr <= qdr) { // daca nodul meu este inclus complet
return aint[nod];
}
int m = (st + dr) / 2;
int ans = 0; // aici calculez raspunsul
if (qst <= m) { // vreau sa merg in stanga
ans = max(ans, query(2 * nod, st, m, qst, qdr));
}
if (qdr > m) { // vreau sa merg in dreapta
ans = max(ans, query(2 * nod + 1, m + 1, dr, qst, qdr));
}
return ans;
}
public:
SegTree(int n, vector<int> a) {
capacity = n;
aint.resize(4 * n, 0);
build(1, 1, n, a);
}
void change(int pos, int val) {
update(1, 1, capacity, pos, val);
}
int getAns(int l, int r) {
return query(1, 1, capacity, l, r);
}
};
int main() {
int n, q;
cin >> n >> q;
vector<int> a(n + 5);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
SegTree aint(n + 5, a);
for (int i = 1; i <= q; i++) {
int type, a, b;
cin >> type >> a >> b;
if (type == 1) {
aint.change(a, b);
} else {
cout << aint.getAns(a, b) << '\n';
}
}
}