use proconio::input;

fn main() {
    input! {
        n: usize,
        q: usize,
        a: [usize; n],
        tq: [(usize, usize, usize); q],
    }
    let mut segtree = SegmentTree::new(&a, std::usize::MIN, |a, b| a.max(b));
    for (t, x, y) in tq {
        match t {
            1 => segtree.update(x - 1, y),
            2 => println!("{}", segtree.query(x - 1, y)),
            3 => println!(
                "{}",
                segtree.bisect_left(x - 1, n, |ai| ai >= y).unwrap_or(n) + 1
            ),
            _ => unreachable!(),
        }
    }
}

struct SegmentTree<T: Copy> {
    n: usize,            // size of data
    f: fn(T, T) -> T,    // binary operation of the monoid
    e: T,                // identity element of the monoid
    binary_tree: Vec<T>, // 1-indexed binary tree (parent: x/2, left_child: x*2, right_child: x*2+1, sibling: x^1, is_x_left_child: x%2==0, is_x_right_child: x%2==1)
}
impl<T: Copy> SegmentTree<T> {
    /// O(n) # create segment tree (e is identity element for a function f in type T)
    fn new(data: &[T], e: T, f: fn(T, T) -> T) -> Self {
        let (n, binary_tree) = (data.len(), vec![e; 2 * data.len().next_power_of_two()]);
        let segment_tree = SegmentTree {
            n,
            f,
            e,
            binary_tree,
        };
        segment_tree.init(data)
    }

    /// O(n) # init segment tree by given data
    fn init(mut self, data: &[T]) -> Self {
        let leaf_offset = self.leaf_offset();
        for (i, &di) in data.iter().enumerate() {
            self.binary_tree[leaf_offset + i] = di;
        }
        for i in (1..leaf_offset).rev() {
            self.binary_tree[i] = (self.f)(self.binary_tree[i * 2], self.binary_tree[i * 2 + 1]);
        }
        self
    }

    /// O(1) # get beginning index of the segment tree leaf
    fn leaf_offset(&self) -> usize {
        self.n.next_power_of_two()
    }

    /// O(log(n)) # set leaf[k] = x, and update segment tree. (non-recursive)
    fn update(&mut self, k: usize, x: T) {
        let mut current = self.leaf_offset() + k;
        self.binary_tree[current] = x;
        while current / 2 > 0 {
            current /= 2;
            self.binary_tree[current] = (self.f)(
                self.binary_tree[current * 2],
                self.binary_tree[current * 2 + 1],
            );
        }
    }

    /// O(log(n)) # calculate f(l, l+1, ..., r-1). note the half interval [l, r). (non-recursive)
    fn query(&self, l: usize, r: usize) -> T {
        let (mut l, mut r) = (self.leaf_offset() + l, self.leaf_offset() + r);
        let mut result = self.e;
        while l < r {
            if l % 2 == 1 {
                result = (self.f)(result, self.binary_tree[l]);
                l += 1; // l is right child, so move to next subtree.
            }
            if r % 2 == 1 {
                result = (self.f)(result, self.binary_tree[r ^ 1]);
            }
            l /= 2;
            r /= 2;
        }
        result
    }

    /// O(log^2(n)) # search the leftmost leaf where cmp(x) is true
    fn bisect_left<F>(&self, l: usize, r: usize, cmp: F) -> Option<usize>
    where
        F: Fn(T) -> bool,
    {
        let (mut from, mut to) = (l, r);
        while to - from > 1 {
            let mid = (from + to) / 2;
            if cmp(self.query(from, mid)) {
                to = mid;
            } else {
                from = mid;
            }
        }
        if cmp(self.binary_tree[self.leaf_offset() + from]) {
            Some(from)
        } else {
            None
        }
    }
}


Measured memory_bytes: 17236