#include <iostream>
#include <fstream>
//#define INFINIT 1000000001 //10^9 + 1
#define NMAX 100000 //10^5
using namespace std;
ifstream fin ("arbint.in");
ofstream fout ("arbint.out");
int tree[4 * NMAX + 1];
int v[NMAX + 1];
void buildArbore(int node, int left, int right){
if(left == right){
//frunza
tree[node] = v[left];
return;
}
int mid = left + (right - left) / 2;
//aici nu mai fac nicio decizie, pur si simplu actualizez fiii si aia e
buildArbore(node * 2, left, mid);
buildArbore(node * 2 + 1, mid + 1, right);
tree[node] = max(tree[node * 2], tree[node * 2 + 1]);
}
int st, dr;
int query(int node, int left, int right){
if(st <= left && right <= dr){
//intervalul in care ma aflu este cuprins in intregime in intervalul de query
return tree[node];
}
///aici ajung doar daca [left, right] nu e complet inclus in [st, dr]
///dar daca am ajuns aici inseamna ca poate sa imi mai ofere ceva intervalul [left, right]
///asa ca decid in care fii sa ma duc
int mid = left + (right - left) / 2;
int rez1 = -1, rez2 = -1; ///virgula intre ele pt ca nu conteaza ordinea
if(st <= mid){
//inseamna ca fiul din stanga mai poate sa ma ajute cu ceva, deci ma duc si in el
rez1 = query(node * 2, left, mid);
}
if(dr >= mid + 1){
//inseamna ca fiul din dreapta mai poate sa ma ajute cu ceva, deci ma duc si in el
rez2 = query(node * 2 + 1, mid + 1, right);
}
return max(rez1, rez2); //adica maximul query-urilor din fii
//iar daca nu ma duc in query intr-un anumit fiu, atunci e -1
//adica il prefera pe celalalt orice ar fi
//si daca am ajuns pana aici, sigur unul din rez1 sau rez2 o sa fie diferit de -1!
//pt ca, la fel cum am zis mai sus, daca am ajuns in [left, right] inseamna ca mai are ceva sa imi ofere
//si daca [left, right] nu e inclus complet in interval, atunci sigur minim unul din fiii lui [left, right] are ceva sa imi ofere (chiar ambii se poate)
}
int pozAct, valAct;
void update(int node, int left, int right){
//vreau sa actualizez maximul din [left, right]
/// !!!!!!! daca am ajuns pana aici, stiu sigur ca elementul pe care vreau sa il actualizez se afla ca pozitie in intervalul [left, right]
if(left == right){
//inseamna ca left = pozAct
tree[node] = valAct;
return;
}
int mid = left + (right - left) / 2;
///vreau sa actualizez fiul care contine pozAct
///stiu deja, conform ce am scris mai sus, ca pozAct e cuprins intre [left, right]
///si acum vreua sa vad daca e in [left, mid] sau in [mid + 1, right]
if(pozAct <= mid){
///e in fiul din stanga atunci
update(node * 2, left, mid);
}
else {
update(node * 2 + 1, mid + 1, right);
}
///acum ca am actualizat unul din fii, actualizez si intervalul [left, right]
tree[node] = max(tree[node * 2], tree[node * 2 + 1]);
}
int main()
{
int N, M;
fin >> N >> M;
for(int i = 1; i <= N; i++){
fin >> v[i];
}
buildArbore(1, 1, N);
for(int q = 1; q <= M; q++){
//cout << "q = " << q << "\n";
int tip, a, b;
fin >> tip >> a >> b;
if(tip == 0){
//query
///variabile globale, ca sa nu ma car cu ele ca parametrii ale functiei
st = a;
dr = b;
fout << query(1, 1, N) << "\n";
}
else {
//update
///variabile globale, ca mai sus
pozAct = a;
valAct = b;
update(1, 1, N);
}
}
return 0;
}