use proconio::input;
use std::{cmp::Reverse, ops::Range};
// ordtools {{{
#[allow(dead_code)]
mod ordtools {
    pub trait Ordtools: PartialOrd + Clone + Sized {
        fn change_min(&mut self, rhs: &Self) {
            if rhs < self {
                *self = rhs.clone();
            }
        }

        fn change_max(&mut self, rhs: &Self) {
            if rhs > self {
                *self = rhs.clone();
            }
        }
    }

    impl<T: PartialOrd + Clone + Sized> Ordtools for T {}
}
// }}}
// rmq {{{
#[allow(dead_code)]
mod rmq {
    use super::ordtools::Ordtools;
    use std::ops::{Bound, Range, RangeBounds};

    // TODO: クレートを分けます。
    trait BitPosition {
        fn pow_of_two(self) -> usize;
    }

    impl BitPosition for usize {
        fn pow_of_two(self) -> usize {
            1 << self
        }
    }

    trait Bit {
        fn flip(self) -> Self;
        fn reverse_and_flip(self) -> Self;
        fn lg_unchecked(self) -> u32;
        fn lg_usize_unchecked(self) -> usize;
        fn lg(self) -> Option<u32>;
        fn lg_usize(self) -> Option<usize>;
        fn lg_ceil(self) -> u32;
        fn lg_ceil_usize(self) -> usize;
    }

    impl Bit for usize {
        fn flip(self) -> Self {
            self ^ std::usize::MAX
        }
        fn reverse_and_flip(self) -> Self {
            self.reverse_bits() ^ std::usize::MAX
        }
        fn lg_ceil(self) -> u32 {
            self.next_power_of_two().trailing_zeros()
        }
        fn lg_ceil_usize(self) -> usize {
            self.lg_ceil() as usize
        }
        fn lg_unchecked(self) -> u32 {
            (self + 1).lg_ceil() - 1
        }
        fn lg_usize_unchecked(self) -> usize {
            self.lg_unchecked() as usize
        }
        fn lg(self) -> Option<u32> {
            if self == 0 {
                None
            } else {
                Some(self.lg_unchecked())
            }
        }
        fn lg_usize(self) -> Option<usize> {
            if self == 0 {
                None
            } else {
                Some(self.lg_usize_unchecked())
            }
        }
    }

    pub struct FastMinimum {
        lca: Box<LeastCommonAncestor>,
    }
    impl FastMinimum {
        pub fn build<T: Ord>(seq: &[T]) -> Self {
            assert!(!seq.is_empty());
            let parent = construct_cartesian_tree(seq);
            let mut tree = vec![vec![]; seq.len()];
            let mut root = 0;
            for (i, &p) in parent.iter().enumerate() {
                if i == p {
                    root = i;
                } else {
                    tree[i].push(p);
                    tree[p].push(i);
                }
            }
            Self {
                lca: Box::new(LeastCommonAncestor::from_tree(root, &tree)),
            }
        }

        pub fn query(&self, range: impl RangeBounds<usize>) -> Option<usize> {
            let start = match range.start_bound() {
                Bound::Excluded(&x) => x + 1,
                Bound::Included(&x) => x,
                Bound::Unbounded => 0,
            };
            let end = match range.end_bound() {
                Bound::Excluded(&x) => {
                    if x == 0 {
                        return None;
                    } else {
                        x - 1
                    }
                }
                Bound::Included(&x) => x,
                Bound::Unbounded => self.lca.vertex_count() - 1,
            };
            if start > end {
                None
            } else {
                Some(self.lca.query(start, end))
            }
        }

        pub fn query_nonempty(&self, range: impl RangeBounds<usize>) -> usize {
            let start = match range.start_bound() {
                Bound::Excluded(&x) => x + 1,
                Bound::Included(&x) => x,
                Bound::Unbounded => 0,
            };
            let end = match range.end_bound() {
                Bound::Excluded(&x) => {
                    assert!(x != 0);
                    x - 1
                }
                Bound::Included(&x) => x,
                Bound::Unbounded => self.lca.vertex_count() - 1,
            };
            assert!(start <= end);
            self.lca.query(start, end)
        }
    }

    pub fn construct_cartesian_tree<T: Ord>(seq: &[T]) -> Vec<usize> {
        let mut p = vec![0; seq.len()];
        let mut stack = Vec::with_capacity(seq.len());
        stack.push(0);
        for x in 1..seq.len() {
            if let Some((j, y)) = stack
                .iter()
                .copied()
                .enumerate()
                .rfind(|&(_, y)| seq[y] <= seq[x])
            {
                p[x] = y;
                if j != stack.len() - 1 {
                    let z = stack[j + 1];
                    p[z] = x;
                }
                stack.truncate(j + 1);
                stack.push(x);
            } else {
                p[stack[0]] = x;
                p[x] = x;
                stack = vec![x];
            }
        }
        p
    }

    #[derive(Debug, Clone, Eq, PartialEq)]
    pub struct LeastCommonAncestor {
        span: Vec<Range<usize>>,
        time_to_id: Vec<usize>,
        compound_table: CompoundTable,
    }

    impl LeastCommonAncestor {
        pub fn vertex_count(&self) -> usize {
            self.span.len()
        }

        pub fn from_tree(root: usize, tree: &[Vec<usize>]) -> Self {
            // dfs に渡したい環境変数を束ねます。
            #[derive(Debug, Eq, PartialEq)]
            struct Env<'a> {
                time: usize,
                time_to_id: Vec<usize>,
                span: Vec<Range<usize>>,
                depth_table: Vec<usize>,
                tree: &'a [Vec<usize>],
            }
            let mut env = Env {
                time: 0,
                time_to_id: vec![0; tree.len() * 2 - 1],
                depth_table: vec![0; tree.len() * 2 - 1],
                span: vec![tree.len() * 2 - 1..0; tree.len()],
                tree,
            };

            fn dfs(x: usize, p: usize, d: usize, env: &mut Env) {
                env.time_to_id[env.time] = x;
                env.depth_table[env.time] = d;
                env.span[x].start.change_min(&env.time);
                env.time += 1;

                for y in env.tree[x].iter().copied().filter(|&y| y != p) {
                    dfs(y, x, d + 1, env);
                    env.time_to_id[env.time] = x;
                    env.depth_table[env.time] = d;
                    env.time += 1;
                }
                env.span[x].end.change_max(&(env.time));
            }
            dfs(root, root, 0, &mut env);

            Self {
                span: env.span,
                time_to_id: env.time_to_id,
                compound_table: CompoundTable::from_vec(env.depth_table),
            }
        }

        pub fn query(&self, u: usize, v: usize) -> usize {
            let Range {
                start: start_u,
                end: end_u,
            } = self.span[u];
            let Range {
                start: start_v,
                end: end_v,
            } = self.span[v];
            self.time_to_id[self
                .compound_table
                .query_nonempty(start_u.min(start_v)..end_u.max(end_v))]
        }
    }

    #[derive(Debug, Clone, Eq, PartialEq)]
    pub struct CompoundTable {
        seq: Vec<usize>,
        block_size: usize,
        pattern: Vec<usize>,
        encyclopedia: Encyclopedia,
        sparse_table: SparseTable<usize>,
    }
    impl CompoundTable {
        #[inline]
        pub fn from_vec(seq: Vec<usize>) -> Self {
            let block_size = seq
                .len()
                .lg_usize()
                .map(|x| ((x as f64) * 0.65) as usize)
                .unwrap_or(1)
                .max(1);
            Self::from_vec_with_block_size(seq, block_size)
        }

        pub fn from_vec_with_block_size(seq: Vec<usize>, block_size: usize) -> Self {
            assert!(block_size <= seq.len());
            assert!(!seq.is_empty());
            assert!(
                (0..seq.len() - 1).all(|i| seq[i] + 1 == seq[i + 1] || seq[i] == seq[i + 1] + 1)
            );

            // ブロックごとのパターンを計算です。
            let mut pattern = vec![0; (seq.len() + block_size - 1) / block_size];
            for (i, x) in pattern.iter_mut().enumerate() {
                let mut coeff = 1;
                for j in block_size * i + 1..(block_size * (i + 1)).min(seq.len()) {
                    if seq[j - 1] > seq[j] {
                        *x += coeff;
                    }
                    coeff += coeff;
                }
            }
            let encyclopedia = Encyclopedia::with_len(block_size);
            let sparse_table = SparseTable::from_vec(
                pattern
                    .iter()
                    .copied()
                    .enumerate()
                    .map(|(i, seq_id)| {
                        seq[block_size * i + encyclopedia.query_nonempty(seq_id, 0..block_size)]
                    })
                    .collect(),
            );

            Self {
                block_size,
                seq,
                pattern,
                encyclopedia,
                sparse_table,
            }
        }

        #[inline]
        pub fn query(&self, range: Range<usize>) -> Option<usize> {
            if range.start >= range.end {
                None
            } else {
                Some(self.query_nonempty(range))
            }
        }

        pub fn query_nonempty(&self, range: Range<usize>) -> usize {
            let Range { start, end } = range;
            assert!(start < end);
            let k = self.block_size;
            let start_block = start / k;
            let start_rem = start - start_block * k;
            let end_block = end / k;
            let end_rem = end - end_block * k;
            if start_block == end_block {
                start_block * self.block_size
                    + self
                        .encyclopedia
                        .query_nonempty(self.pattern[start_rem], start_rem..end_rem)
            } else {
                let left = self
                    .encyclopedia
                    .query(self.pattern[start_block], start_rem..self.block_size)
                    .map(|i| start_block * self.block_size + i);
                let center =
                    self.sparse_table
                        .query(start_block + 1..end_block)
                        .map(|central_block| {
                            central_block * self.block_size
                                + self
                                    .encyclopedia
                                    .query_nonempty(self.pattern[central_block], 0..self.block_size)
                        });
                let right = self
                    .pattern
                    .get(end_block)
                    .and_then(|&pattern| self.encyclopedia.query(pattern, 0..end_rem))
                    .map(|i| end_block * self.block_size + i);
                [left, center, right]
                    .iter()
                    .flatten()
                    .copied()
                    .min_by_key(|&i| self.seq[i]) // std::cmp::min は同点の場合 first が優先です。
                    .unwrap()
            }
        }
    }

    #[derive(Debug, Clone, Eq, PartialEq)]
    pub struct SparseTable<T: Ord + std::fmt::Debug> {
        pub seq: Vec<T>,
        table: Vec<Vec<usize>>,
    }

    impl<T: Ord + std::fmt::Debug> SparseTable<T> {
        pub fn from_vec(seq: Vec<T>) -> Self {
            let n = seq.len();
            let h = n.lg_usize().unwrap_or(0) + 1;
            let mut table = vec![vec![0; n]; h];
            table[0] = (0..n).collect();
            let mut d = 1;
            for i in 1..h {
                let (left, right) = table.split_at_mut(i);
                let crr = &mut right[0];
                crr.copy_from_slice(&left[i - 1]);
                for j in d..seq.len() {
                    if seq[crr[j - d]] > seq[crr[j]] {
                        crr[j - d] = crr[j];
                    }
                }
                d += d;
            }
            Self { table, seq }
        }

        #[inline]
        pub fn query(&self, range: Range<usize>) -> Option<usize> {
            if range.start >= range.end {
                None
            } else {
                Some(self.query_nonempty(range))
            }
        }

        pub fn query_nonempty(&self, range: Range<usize>) -> usize {
            let Range { start, end } = range;
            assert!(start < end);
            let d = match end - start {
                1 => 1,
                _ => ((end - start).next_power_of_two() as usize) / 2,
            };
            assert!(d <= end - start && end - start <= 2 * d);
            let row = &self.table[d.trailing_zeros() as usize];
            let mut ans = row[start];
            if let Some(&x) = row.get(end - d) {
                // ans < x は保証されています。
                if self.seq[ans] > self.seq[x] {
                    ans = x;
                }
            }
            ans
        }
    }

    #[derive(Debug, Clone, Eq, PartialEq)]
    pub struct Encyclopedia {
        len: usize,
        table: Vec<Vec<usize>>,
    }

    impl Encyclopedia {
        pub fn with_len(len: usize) -> Self {
            Self {
                len,
                table: if len == 0 {
                    vec![vec![]]
                } else {
                    let mut ans = vec![vec![0; len]; (len - 1).pow_of_two()];
                    let mut a: Vec<usize> = (0..len).collect();
                    for ans in ans.iter_mut() {
                        // 累積 min-position を計算していきます。
                        ans[0] = 0;
                        for i in 1..len {
                            let j = ans[i - 1];
                            ans[i] = if a[j] <= a[i] { j } else { i };
                        }

                        // a を次のイテレーションで使うものに変えます。
                        // もっとも左の Pos を Neg に変えて、それより前をすべて Pos に変えます。
                        if let Some(s) = (0..len - 1).find(|&i| a[i] < a[i + 1]) {
                            a[s] = a[s + 1] + 1;
                            (0..s).rev().for_each(|i| a[i] = a[i + 1] - 1);
                        }
                    }
                    ans
                },
            }
        }

        #[inline]
        pub fn query(&self, seq_id: usize, range: Range<usize>) -> Option<usize> {
            if range.start >= range.end {
                None
            } else {
                Some(self.query_nonempty(seq_id, range))
            }
        }

        pub fn query_nonempty(&self, seq_id: usize, range: Range<usize>) -> usize {
            let Range { start, end } = range;
            assert!(start < end);
            start + self.table[seq_id >> start][end - start - 1]
        }
    }

    pub fn construct_seq_from_seq_id(len: usize, seq_id: usize) -> Vec<usize> {
        assert!(seq_id < len.pow_of_two());
        let mut ans = vec![2 * len; len]; // あとで調整するので、とりあえず大きめの値にしておきます。`
        let mut x = seq_id;
        for i in 0..len - 1 {
            ans[i + 1] = if x % 2 == 0 { ans[i] + 1 } else { ans[i] - 1 };
            x /= 2;
        }
        let diff = ans[len - 1] - (len - 1);
        ans.iter_mut().for_each(|x| *x -= diff);
        ans
    }
}
// }}}
use rmq::SparseTable;

fn cal(
    sparse_table: &[SparseTable<u32>],
    range: Range<usize>,
) -> (Reverse<u32>, Reverse<u32>, usize, usize) {
    let Range { start, end } = range;
    let parity = start % 2;
    let anti_parity = 1 - parity;
    let u = parity + 2 * sparse_table[parity].query(start / 2..end / 2).unwrap();
    let v = anti_parity
        + 2 * sparse_table[anti_parity]
            .query((u + 1) / 2..(end + 1) / 2)
            .unwrap();
    (
        Reverse(sparse_table[parity].seq[u / 2]),
        Reverse(sparse_table[anti_parity].seq[v / 2]),
        u,
        v,
    )
}
fn main() {
    input!(n: usize, a: [u32; n]);
    let sparse_table: Vec<SparseTable<u32>> = (0..2)
        .map(|i| SparseTable::from_vec((0..n).filter(move |&j| j % 2 == i).map(|j| a[j]).collect()))
        .collect();
    let mut ans = Vec::new();
    let mut heap = std::collections::BinaryHeap::from(vec![(cal(&sparse_table, 0..n), 0, n)]);
    while let Some(((au, av, u, v), start, end)) = heap.pop() {
        ans.push((au, av));
        for subrange in [start..u, u + 1..v, v + 1..end].iter() {
            let &Range { start, end } = subrange;
            if start < end {
                heap.push((cal(&sparse_table, start..end), start, end));
            }
        }
    }
    println!(
        "{}",
        ans.iter()
            .map(|&(Reverse(x), Reverse(y))| format!("{} {}", x, y))
            .fold(String::new(), |acc, x| if acc.is_empty() {
                x
            } else {
                acc + " " + x.as_ref()
            })
    );
}


Measured memory_bytes: 35900