#include <bits/stdc++.h>

using namespace std;
using int64 = long long;

constexpr int DEBUG = 0;

struct NodeValue {
  int index;
  int value;
  NodeValue(int index, int value) : index(index), value(value) {}
};

NodeValue MinNodeValue(const NodeValue& v1, const NodeValue& v2) {
  if (v1.value < v2.value) return v1;
  else return v2;
};

struct Node {
  NodeValue min_0;
  NodeValue min_1;
  Node(NodeValue min_0, NodeValue min_1) : min_0(min_0), min_1(min_1) {}
};

// A lazy-propagation segment tree for range-operation and range-get.
// T: The type of values.
// U: The type of operators.
// Three conditions.
// - apply_op_fn(merge_fn(x1, x2), o) = merge_fn(apply_op_fn(x1, o), apply_op_fn(x2, o))
// - apply_op_fn(apply_op_fn(x, o1), o2) = apply_op_fn(x, merge_op_fn(o1, o2))
// - apply_op_fn(x, empty_op) = x
// Verified: AOJ-DSL2F, AOJ-DSL2G, AOJ-DSL2H, AOJ-DSL2I
template<typename T, typename U>
class RORGSegmentTree {
 private:
  function<T(T, T)> merge_fn;
  T empty_value;
  function<U(U, U)> op_merge_fn;
  U empty_op;
  function<T(T, U)> apply_op_fn;
  int num_nodes;
  vector<T> nodes;
  vector<U> op_nodes;

  void DoLazyPropagation(int node_index) {
   if (op_nodes[node_index] != empty_op) {
      nodes[node_index] = apply_op_fn(nodes[node_index], op_nodes[node_index]);
      if (node_index < num_nodes / 2) {
        op_nodes[2 * node_index] =
            op_merge_fn(op_nodes[2 * node_index], op_nodes[node_index]);
        op_nodes[2 * node_index + 1] =
            op_merge_fn(op_nodes[2 * node_index + 1], op_nodes[node_index]);
      }
      op_nodes[node_index] = empty_op;
    }
  }

  void ApplyOpRangeInternal(
      int node_index, int node_l, int node_r, int l, int r, U op) {
    DoLazyPropagation(node_index);

    if (node_r <= l || r <= node_l) return;

    if (l <= node_l && node_r <= r) {
      op_nodes[node_index] = op;
      DoLazyPropagation(node_index);
      return;
    }
    int node_m = (node_l + node_r) / 2;
    ApplyOpRangeInternal(2 * node_index, node_l, node_m, l, r, op);
    ApplyOpRangeInternal(2 * node_index + 1, node_m, node_r, l, r, op);
    nodes[node_index] =
        merge_fn(nodes[2 * node_index], nodes[2 * node_index + 1]);
  }

  T GetRangeInternal(
      int node_index, int node_l, int node_r, int l, int r) {
    DoLazyPropagation(node_index);

    if (node_r <= l || r <= node_l) return empty_value;

    if (l <= node_l && node_r <= r) {
      return nodes[node_index];
    }

    int node_m = (node_l + node_r) / 2;
    return merge_fn(
        GetRangeInternal(2 * node_index, node_l, node_m, l, r),
        GetRangeInternal(2 * node_index + 1, node_m, node_r, l, r));
  }

 public:
  RORGSegmentTree(
      function<T(T, T)> merge_fn, T empty_value,
      function<U(U, U)> op_merge_fn, U empty_op,
      function<T(T, U)> apply_op_fn, vector<T> values) :
          merge_fn(merge_fn), empty_value(empty_value),
          op_merge_fn(op_merge_fn), empty_op(empty_op),
          apply_op_fn(apply_op_fn) {
    int n = values.size();
    num_nodes = 1;
    while (num_nodes < n) num_nodes *= 2;
    num_nodes *= 2;
    nodes = vector<T>(num_nodes, empty_value);
    op_nodes = vector<U>(num_nodes, empty_op);
    for (int i = 0; i < values.size(); ++i) {
      nodes[num_nodes / 2 + i] = values[i];
    }
    for (int i = num_nodes / 2 - 1; i >= 1; i--) {
      nodes[i] = merge_fn(nodes[2 * i], nodes[2 * i + 1]);
    }
  }

  // Applies the operator to the range [l, r).
  void ApplyOpRange(int l, int r, U op) {
    return ApplyOpRangeInternal(1, 0, num_nodes / 2, l, r, op);
  }

  T GetRange(int l, int r) {
    return GetRangeInternal(1, 0, num_nodes / 2, l, r);
  }
  
  T Get(int i) { return GetRange(i, i + 1); }

  // Updates the i-th element to x.
  void Update(int i, T x) {
    Get(i);  // This is a trick to do lazy propagations.
    nodes[num_nodes / 2 + i] = x;
    for (int cur = (num_nodes / 2 + i) / 2; cur > 0; cur /= 2) {
      DoLazyPropagation(2 * cur);
      DoLazyPropagation(2 * cur + 1);
      nodes[cur] = merge_fn(nodes[2 * cur], nodes[2 * cur + 1]);
    }
  }
};

struct Range {
  int l, r;
  Range(int l, int r) : l(l), r(r) {}
  Range(): l(-1), r(-1) {}
  bool operator== (Range o) const {
    return l == o.l && r == o.r;
  }
  bool operator!= (Range o) const {
    return !((*this) == o);
  }
};

// A variant of a lazy propagation segment tree for range-update and point-get.
// T: The type of values. T needs to have a default constructor.
template<typename T>
class RUPGSegmentTree {
 private:
  tuple<T, bool> MergeUpdates(tuple<T, bool> u1, tuple<T, bool> u2) {
    if (get<1>(u2)) return u2;
    return u1;
  }

  T ApplyUpdate(T v, tuple<T, bool> u) {
    if (get<1>(u)) return get<0>(u);
    return v;
  }

  const tuple<T, bool> EMPTY_UPDATE = make_tuple(T(), false);

  int num_nodes;
  vector<T> values;
  vector<tuple<T, bool>> op_nodes;

  void DoLazyPropagation(int node_index) {
   if (get<1>(op_nodes[node_index])) {
      if (node_index < num_nodes / 2) {
        op_nodes[2 * node_index] =
            MergeUpdates(op_nodes[2 * node_index], op_nodes[node_index]);
        op_nodes[2 * node_index + 1] =
            MergeUpdates(op_nodes[2 * node_index + 1], op_nodes[node_index]);
      } else {
        int value_index = node_index - num_nodes / 2;
        values[value_index] =
            ApplyUpdate(values[value_index], op_nodes[node_index]);
      }
      op_nodes[node_index] = EMPTY_UPDATE;
    }
  }

  void ApplyOpRangeInternal(
      int node_index, int node_l, int node_r, int l, int r, T op) {
    DoLazyPropagation(node_index);

    if (node_r <= l || r <= node_l) return;

    if (l <= node_l && node_r <= r) {
      op_nodes[node_index] = make_tuple(op, true);
      DoLazyPropagation(node_index);
      return;
    }
    int node_m = (node_l + node_r) / 2;
    ApplyOpRangeInternal(2 * node_index, node_l, node_m, l, r, op);
    ApplyOpRangeInternal(2 * node_index + 1, node_m, node_r, l, r, op);
  }

  T GetInternal(int node_index, int node_l, int node_r, int i) {
    DoLazyPropagation(node_index);

    if (node_index >= num_nodes / 2) {
      return values[i];
    }

    int node_m = (node_l + node_r) / 2;
    if (i < node_m) {
      return GetInternal(2 * node_index, node_l, node_m, i);
    } else {
      return GetInternal(2 * node_index + 1, node_m, node_r, i);
    }
  }

 public:
  RUPGSegmentTree(vector<T> in_values) : values(in_values) {
    int n = values.size();
    num_nodes = 1;
    while (num_nodes < n) num_nodes *= 2;
    num_nodes *= 2;
    op_nodes = vector<tuple<T, bool>>(num_nodes, EMPTY_UPDATE);
  }

  // Applies the operator to the range [l, r).
  void ApplyOpRange(int l, int r, T op) {
    if (l >= r) return;
    return ApplyOpRangeInternal(1, 0, num_nodes / 2, l, r, op);
  }

  T Get(int i) { return GetInternal(1, 0, num_nodes / 2, i); }
};

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);

  int n;
  cin >> n;
  vector<int> ps(n);
  for (int i = 0; i < n; i++) {
    cin >> ps[i];
  }

  constexpr int INF_INT_VALUE = 1 << 20;
  const NodeValue INF_NODE_VALUE = NodeValue(-1, INF_INT_VALUE);
  const Node INF_NODE = Node(INF_NODE_VALUE, INF_NODE_VALUE);
  vector<Node> leaf_nodes;
  for (int i = 0; i < n; i++) {
    if (i % 2 == 0) {
      leaf_nodes.push_back(Node(NodeValue(i, ps[i]), INF_NODE_VALUE));
    } else {
      leaf_nodes.push_back(Node(INF_NODE_VALUE, NodeValue(i, ps[i])));
    }
  }
  auto merge_fn = [](const Node& node1, const Node& node2) {
    return Node(
        MinNodeValue(node1.min_0, node2.min_0),
        MinNodeValue(node1.min_1, node2.min_1));
  };
  auto op_merge_fn = [](bool b1, bool b2) {
    return b1 ^ b2;
  };
  const bool EMPTY_OP_VALUE = false;
  auto apply_op_fn = [](const Node& node, bool op) -> Node {
    if (op) {
      return Node(node.min_1, node.min_0);
    } else {
      return node;
    }
  };
  RORGSegmentTree<Node, bool> tree1(
      merge_fn, INF_NODE, op_merge_fn, EMPTY_OP_VALUE, apply_op_fn, leaf_nodes);

  auto merge_update_fn = [](Range current_update, Range new_update) -> Range {
    return new_update;
  };
  const Range NO_UPDATE = Range(-1, -1);
  auto apply_update_fn = [](Range current_value, Range new_value) -> Range {
    if (new_value.l == -1 && new_value.r == -1) return current_value; 
    return new_value;
  };
  vector<Range> ranges;
  for (int i = 0; i < n; i++) {
    ranges.push_back(Range(0, n));
  }
  RUPGSegmentTree<Range> tree2(ranges);

  vector<int> outputs;
  for (int iter = 0; iter < n / 2; iter++) {
    int l = tree1.GetRange(0, n).min_0.index;
    // int r = tree.GetRange(l + 1, n).min_1.index;
    int r = tree1.GetRange(l + 1, tree2.Get(l).r).min_1.index;
    if (DEBUG) cout << "l: " << l << " r: " << r << endl;
    if (DEBUG) cout << ps[l] << " " << ps[r] << endl;
    outputs.push_back(ps[l]);
    outputs.push_back(ps[r]);

    // Update the value l.
    tree1.Update(l, INF_NODE);
    // Update the value r.
    tree1.Update(r, INF_NODE);
    // Flip the range [l + 1, r - 1]
    tree1.ApplyOpRange(l + 1, r, true);

    int p_l = tree2.Get(l).l;
    int p_r = tree2.Get(r).r;
    tree2.ApplyOpRange(p_l, l, Range(p_l, l));
    tree2.ApplyOpRange(l, l + 1, Range(l, l + 1));
    tree2.ApplyOpRange(l + 1, r, Range(l + 1, r));
    tree2.ApplyOpRange(r, r + 1, Range(r, r + 1));
    tree2.ApplyOpRange(r + 1, p_r, Range(r + 1, p_r));

    if (DEBUG) {
      if (iter == 5) exit(1);
    }
  }

  for (int i = 0; i < n; i++) {
    cout << outputs[i] << " ";
  }
  cout << endl;
}


Measured memory_bytes: 26420