#include <bits/stdc++.h>

using namespace std;

using int64 = long long;

// Does coordinate compression.
// Arguments:
// - raw_xs: Values to compress.
// Returns:
// - xs: Compressed values.
// - to_raw: A mapping from a compressed value to a raw value.
// Verified: CF573Div1D
tuple<vector<int>, vector<int>> Compress(const vector<int>& raw_xs) {
  unordered_set<int> raw_x_set(raw_xs.begin(), raw_xs.end());
  vector<int> to_raw(raw_x_set.begin(), raw_x_set.end());
  sort(to_raw.begin(), to_raw.end());
  unordered_map<int, int> to_index;
  for (int i = 0; i < to_raw.size(); i++) {
    to_index[to_raw[i]] = i;
  }
  vector<int> xs;
  for (int raw_x : raw_xs) {
    xs.push_back(to_index[raw_x]);
  }
  return make_tuple(xs, to_raw);
}

constexpr int64 P = 998244353;

// Verified: ABC133E, ABC132D, ABC130E
struct FiniteField {
 private:
  int64 x;
 public:
  FiniteField(int64 input_x) : x(input_x) {}
  FiniteField() : x(0) {}
  int64 Value() { return x; }
  inline FiniteField operator+(FiniteField o) {
    FiniteField r(*this); r += o; return r;
  }
  inline FiniteField operator-(FiniteField o) {
    FiniteField r(*this); r -= o; return r;
  }
  inline FiniteField operator* (FiniteField o) {
    FiniteField r(*this); r *= o; return r;
  }
  inline FiniteField operator/ (FiniteField o) {
    FiniteField r(*this); r /= o; return r;
  }
  inline void operator+= (FiniteField o) { x = (x + o.x) % P; }
  inline void operator-= (FiniteField o) { x = (x + P - o.x) % P; }
  inline void operator*= (FiniteField o) { x = (x * o.x) % P; }
  void operator/=(FiniteField o) {
    int64 p = P - 2; while (p) { if (p % 2) { *this *= o; } o *= o; p /= 2; }
  }
};

template <typename T>
class SegmentTree {
 private:
  function<T(T, T)> merge;
  T empty_value;
  vector<T> nodes;
  int num_nodes;

  // Returns the fold of overwrap between [node_l, node_r) and [l, r).
  // nodes[node_index] needs to correspond to [node_l, node_r).
  T GetRangeInternal(int node_index, int node_l, int node_r, int l, int r) {
    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(
        GetRangeInternal(2 * node_index, node_l, node_m, l, r),
        GetRangeInternal(2 * node_index + 1, node_m, node_r, l, r));
  }

 public:
  // Arguments:
  // in_merge: The function to merge two values. It should be a monoid.
  // in_empty_value: The identity of the monoid.
  // xs: The initial values.
  SegmentTree(
      function<T(T, T)> in_merge, T in_empty_value, const vector<T>& xs) :
          merge(in_merge), empty_value(in_empty_value) {
    int n = xs.size();
    num_nodes = 1;
    while (num_nodes < n) num_nodes *= 2;
    num_nodes *= 2;
    nodes = vector<T>(num_nodes, empty_value);
    for (int i = 0; i < xs.size(); ++i) {
      nodes[num_nodes / 2 + i] = xs[i];
    }
    for (int i = num_nodes / 2 - 1; i >= 1; i--) {
      nodes[i] = merge(nodes[2 * i], nodes[2 * i + 1]);
    }
  }

  // Updates the i-th element to x.
  void Update(int i, T x) {
    nodes[num_nodes / 2 + i] = x;
    for (int cur = (num_nodes / 2 + i) / 2; cur > 0; cur /= 2) {
      nodes[cur] = merge(nodes[2 * cur], nodes[2 * cur + 1]);
    }
  }

  // Returns the fold of the range [l, r) by the merge function.
  T GetRange(int l, int r) {
    return GetRangeInternal(1, 0, num_nodes / 2, l, r);
  }

  T Get(int i) {
    return nodes[num_nodes / 2 + i];
  }
};

struct Int2D {
  int x, y;
};

constexpr int DEBUG = 0;

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

  int n;
  cin >> n;
  vector<int> raw_xs(n), raw_ys(n);
  for (int i = 0; i < n; i++) {
    cin >> raw_xs[i];
    cin >> raw_ys[i];
  }
  
  vector<int> xs, ys;
  tie(xs, ignore) = Compress(raw_xs);
  tie(ys, ignore) = Compress(raw_ys);

  vector<Int2D> ps;
  for (int i = 0; i < n; i++) {
    ps.push_back(Int2D({xs[i], ys[i]}));
  }
  sort(ps.begin(), ps.end(), [](Int2D p1, Int2D p2) {
    return p1.y < p2.y;
  });

  function<int(int, int)> merge_fn = [](int x, int y) { return x + y; };
  int empty_value = 0;

  SegmentTree<int> l_tree(merge_fn, empty_value, vector<int>(n));
  SegmentTree<int> u_tree(merge_fn, empty_value, vector<int>(n, 1));

  vector<FiniteField> two_powers(n + 1);
  two_powers[0] = 1;
  for (int i = 1; i <= n; i++) two_powers[i] = two_powers[i - 1] * 2;

  FiniteField ans = 0;
  for (int i = 0; i < n; i++) {
    ans += two_powers[n] - 1;
    if (DEBUG) cout << ans.Value() << endl;

    u_tree.Update(ps[i].x, 0);

    int ll = l_tree.GetRange(0, ps[i].x);
    int ul = l_tree.GetRange(ps[i].x + 1, n);
    int lu = u_tree.GetRange(0, ps[i].x);
    int uu = u_tree.GetRange(ps[i].x + 1, n);
    if (DEBUG) cout << "ll: " << ll << " ul: " << ul << " lu: " << lu << " uu: " << uu << endl;
    ans -= two_powers[ll + lu] - 1;
    ans -= two_powers[ul + uu] - 1;
    ans -= two_powers[ll + ul] - 1;
    ans -= two_powers[lu + uu] - 1;
    ans += two_powers[ll] - 1;
    ans += two_powers[ul] - 1;
    ans += two_powers[lu] - 1;
    ans += two_powers[uu] - 1;

    l_tree.Update(ps[i].x, 1);

    if (DEBUG) cout << "i: " << i << " ans: " << ans.Value() << endl;
  }
  cout << ans.Value() << endl;
}


Measured memory_bytes: 22060