//线段树叶子结点那一层 是初始的那些结点。
//除叶节点外 点的val根据题意求(update里的pushup,que),看求和还是最值
//线段树要开四倍空间!!!!
//所有都按rt,l,r的顺序写就不会对不上错了
//update和que都是3种情况按同一种顺序写就不会漏了(1.不在区间内 2.刚好找到 3.通过mid左右)。 不在区间的一定单独写return,否则会函数区间越界,从而死循环出不来
//建树,叶节点是出口。先建左右子树再pushup根 。 build单独写(若数据初始值不是全1或0,则在读完初始值后写) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n,m,a[maxn];
struct tt{
int val;//区间更新,还要lazy
}t[maxn<<2];
void pushup(int rt){
//pushup根据题意改,看求和还是最值
//t[rt].val=t[rt<<1].val+t[rt<<1|1].val;
t[rt].val=max(t[rt<<1].val,t[rt<<1|1].val);
}
void build(int rt,int l,int r){
//t[rt].lazy=0;
if(l==r) {
t[rt].val=a[l];
//初始值用数组存时(若初始全1或0,则=1或0)
return;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
pushup(rt);
}
/*lazy才有pushdown //pushdown在update和que的 求mid后
void pushdpwn(int rt,int ln,int rn){}
*/
void update(int x,int k,int rt,int l,int r){
if(x<l||x>r) return;
if(l==r){
t[rt].val=k;
return;
}
int mid=(l+r)>>1;
update(x,k,rt<<1,l,mid);
update(x,k,rt<<1|1,mid+1,r);
pushup(rt);//更新或者建树 左右完了都要pushup根
}
int que(int L,int R,int rt,int l,int r){
if(L>r||R<l) return 0;
if(L<=l&&R>=r) return t[rt].val;
int mid=(l+r)>>1;
//下面这步也根据题意改,求和或取最值
return max(que(L,R,rt<<1,l,mid),que(L,R,rt<<1|1,mid+1,r));
}
int main(){
char ch;
int x,y;
while(cin>>n>>m){
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
while(m--){
getchar();//去掉数字和字符之间的\n
scanf("%c %d%d",&ch,&x,&y);
if(ch=='Q')
printf("%d\n",que(x,y,1,1,n));
else update(x,y,1,1,n);
}
}
}