use std::error::Error;
use std::io::{self, BufReader};
use std::io::prelude::*;
use std::str::{self, FromStr};

fn exec() {
    let mut sc = Scanner::new(io::stdin());
    let n: usize = sc.ne();
    let (mut even, mut odd) = {
        let mut e = Vec::new();
        let mut o = Vec::new();
        for i in 0..n {
            let x: i32 = sc.ne();
            if (i & 1) == 0 {
                e.push((x, i));
            } else {
                o.push((x, i));
            }
        }
        (SegTree::fromseq(&e), SegTree::fromseq(&o))
    };
    enum Qtype {
        A,
        B(bool),
    };
    let mut q = |from: usize, to: usize, qt: Qtype| -> (i32, usize) {
        let is_even = match qt {
            Qtype::A => (from & 1) == 0,
            Qtype::B(b) => b,
        };
        if is_even {
            even.query((from + 1) / 2, (to + 1) / 2)
        } else {
            odd.query(from / 2, to / 2)
        }
    };
    let mut heap = std::collections::BinaryHeap::new();
    heap.push((0, (0, n)));
    let mut ans = Vec::new();
    while let Some((_, (from, to))) = heap.pop() {
        if from + 1 >= to {
            continue;
        }
        if ans.len() == n {
            break;
        }
        let x = q(from, to, Qtype::A);
        let y = q(x.1 + 1, to, Qtype::B((from & 1) == 1));
        ans.push(x.0);
        ans.push(y.0);
        heap.push((-q(from, x.1, Qtype::A).0, (from, x.1)));
        heap.push((-q(x.1 + 1, y.1, Qtype::A).0, (x.1 + 1, y.1)));
        heap.push((-q(y.1 + 1, to, Qtype::A).0, (y.1 + 1, to)));
    }
    for x in ans {
        print!("{} ", x);
    }
    println!("");
}

// Range Minimum Query
type Val = (i32, usize);
impl Monoid<Val> for Val {
    fn merge(l: &Val, r: &Val) -> Val {
        if l.0 < r.0 { *l } else { *r }
    }
    fn ide() -> Val {
        (std::i32::MAX, 0)
    }
}

#[allow(dead_code)]
struct SegTree<T> {
    n: usize,
    data: Vec<T>,
}

// 根ノードを1とする
#[allow(dead_code)]
impl<T: Monoid<T>> SegTree<T> {
    fn new(n_: usize) -> SegTree<T> {
        let mut res_n = 1;
        while res_n < n_ {
            res_n <<= 1;
        }
        SegTree::<T> {
            n: res_n,
            data: vec![T::ide(); 2 * res_n],
        }
    }
    fn fromseq(seq: &Vec<T>) -> SegTree<T> {
        let n_ = seq.len();
        let mut res_seg = SegTree::<T>::new(n_);
        for i in 0..n_ {
            res_seg.data[i + res_seg.n] = seq[i].clone();
        }
        for i in (1..res_seg.n).rev() {
            res_seg.data[i] = T::merge(&res_seg.data[2 * i], &res_seg.data[2 * i + 1])
        }
        res_seg
    }
    fn update(&mut self, k_: usize, x: T) {
        let mut k = k_ + self.n;
        self.data[k] = x;
        while k > 0 {
            k >>= 1;
            self.data[k] = T::merge(&self.data[k * 2], &self.data[k * 2 + 1]);
        }
    }
    fn query(&mut self, ql: usize, qr: usize) -> T {
        let n_ = self.n;
        self.__query(ql, qr, 1, 0, n_)
    }
    fn __query(&mut self, ql: usize, qr: usize, k: usize, nl: usize, nr: usize) -> T {
        if nr <= ql || qr <= nl {
            return T::ide();
        };
        if ql <= nl && nr <= qr {
            return self.data[k].clone();
        };
        let mid = (nl + nr) / 2;
        let vl = self.__query(ql, qr, k * 2, nl, mid);
        let vr = self.__query(ql, qr, k * 2 + 1, mid, nr);
        T::merge(&vl, &vr)
    }
}


trait Monoid<T>: Clone {
    fn merge(&T, &T) -> T;
    fn ide() -> T;
}

fn main() {
    const STACK: usize = 16 * 1024 * 1024;
    let _ = std::thread::Builder::new()
        .stack_size(STACK)
        .spawn(|| { exec(); })
        .unwrap()
        .join()
        .unwrap();
}

#[allow(dead_code)]
pub struct Scanner<R> {
    bufread: BufReader<R>,
    id: usize,
    buf: Vec<u8>,
}
impl<R: Read> Scanner<R> {
    pub fn new(resource: R) -> Scanner<R> {
        Scanner {
            bufread: BufReader::new(resource),
            id: 0,
            buf: Vec::new(),
        }
    }
    fn next_line(&mut self) -> Option<String> {
        let mut res = String::new();
        match self.bufread.read_line(&mut res) {
            Ok(0) => None,
            Ok(_) => Some(res),
            Err(why) => panic!("error in read_line: {}", why.description()),
        }
    }
    pub fn next<T: FromStr>(&mut self) -> Option<T> {
        while self.buf.is_empty() {
            self.buf = match self.next_line() {
                Some(r) => {
                    self.id = 0;
                    r.trim().as_bytes().to_owned()
                }
                None => return None,
            };
        }
        let l = self.id;
        assert_ne!(self.buf[l], b' ');
        let n = self.buf.len();
        let mut r = l;
        while r < n && self.buf[r] != b' ' {
            r += 1;
        }
        let res = match str::from_utf8(&self.buf[l..r]).ok().unwrap().parse::<T>() {
            Ok(s) => Some(s),
            Err(_) => {
                panic!(
                    "parse error, {:?}",
                    String::from_utf8(self.buf[l..r].to_owned())
                )
            }
        };
        while r < n && self.buf[r] == b' ' {
            r += 1;
        }
        if r == n {
            self.buf.clear();
        } else {
            self.id = r;
        }
        res
    }
    pub fn ne<T: FromStr>(&mut self) -> T {
        self.next::<T>().unwrap()
    }
}


Measured memory_bytes: 27480