#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<int, int>;
using pil = pair<int, ll>;
using pli = pair<ll, int>;
using pll = pair<ll, ll>;
const int MOD = 998244353;
const int inf = (1 << 30) - 1;
const ll INF = (1LL << 60) - 1;
const double pi = acos(-1.0);
const double EPS = 1e-10;
template <typename T>
bool chmax(T &x, const T &y) {
  return (x < y) ? (x = y, true) : false;
};
template <typename T>
bool chmin(T &x, const T &y) {
  return (x > y) ? (x = y, true) : false;
};
template <int mod>
struct Mod_Int {
  ll x;
  Mod_Int() : x(0) {}
  Mod_Int(ll y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
  Mod_Int &operator+=(const Mod_Int &p) {
    x = (x + p.x) % mod;
    return *this;
  }
  Mod_Int &operator-=(const Mod_Int &p) {
    x = (x + mod - p.x) % mod;
    return *this;
  }
  Mod_Int &operator*=(const Mod_Int &p) {
    x = (x * p.x) % mod;
    return *this;
  }
  Mod_Int &operator/=(const Mod_Int &p) {
    *this *= p.inverse();
    return *this;
  }
  Mod_Int &operator++() { return *this += Mod_Int(1); }
  Mod_Int operator++(int) {
    Mod_Int tmp = *this;
    ++*this;
    return tmp;
  }
  Mod_Int &operator--() { return *this -= Mod_Int(1); }
  Mod_Int operator--(int) {
    Mod_Int tmp = *this;
    --*this;
    return tmp;
  }
  Mod_Int operator-() const { return Mod_Int(-x); }
  Mod_Int operator+(const Mod_Int &p) const { return Mod_Int(*this) += p; }
  Mod_Int operator-(const Mod_Int &p) const { return Mod_Int(*this) -= p; }
  Mod_Int operator*(const Mod_Int &p) const { return Mod_Int(*this) *= p; }
  Mod_Int operator/(const Mod_Int &p) const { return Mod_Int(*this) /= p; }
  bool operator==(const Mod_Int &p) const { return x == p.x; }
  bool operator!=(const Mod_Int &p) const { return x != p.x; }
  Mod_Int inverse() const {
    assert(*this != Mod_Int(0));
    return pow(mod - 2);
  }
  Mod_Int pow(ll k) const {
    Mod_Int now = *this, ret = 1;
    while (k) {
      if (k & 1) ret *= now;
      now *= now, k >>= 1;
    }
    return ret;
  }
  friend ostream &operator<<(ostream &os, const Mod_Int &p) {
    return os << p.x;
  }
  friend istream &operator>>(istream &is, Mod_Int &p) {
    ll a;
    is >> a;
    p = Mod_Int<mod>(a);
    return is;
  }
};
template <typename T>
struct combination {
  vector<T> _fac, _ifac;
  combination(int n) {
    _fac.resize(n + 1), _ifac.resize(n + 1);
    _fac[0] = 1;
    for (int i = 1; i <= n; i++) _fac[i] = _fac[i - 1] * i;
    _ifac[n] = _fac[n].inverse();
    for (int i = n; i >= 1; i--) _ifac[i - 1] = _ifac[i] * i;
  }
  T fac(int k) const { return _fac[k]; }
  T ifac(int k) const { return _ifac[k]; }
  T comb(int n, int k) const {
    if (k < 0 || n < k) return 0;
    return fac(n) * ifac(n - k) * ifac(k);
  }
  T perm(int n, int k) const {
    if (k < 0 || n < k) return 0;
    return fac(n) * ifac(n - k);
  }
  T second_stirling_number(ll n, int k) {
    T ret = 0;
    for (int i = 0; i < k + 1; i++) {
      T tmp = comb(k, i) * T(k - i).pow(n);
      ret += ((i & 1) ? -tmp : tmp);
    }
    return ret * ifac(k);
  }
};
using mint = Mod_Int<MOD>;
template <int mod, int primitive_root>
struct Number_Theorem_Transform {
  using Mint = Mod_Int<mod>;
  const int y, x;
  vector<Mint> pw;
  Number_Theorem_Transform() : y(__builtin_ctz(mod - 1)), x(mod >> y) {
    pw.resize(y);
    pw[0] = Mint(primitive_root).pow(x);
    for (int i = 0; i < y - 1; i++) pw[i + 1] = pw[i] * pw[i];
  }
  void ntt(vector<Mint> &a, int n, bool inverse = false) const {
    assert((n & (n - 1)) == 0);
    a.resize(n);
    for (int i = 0; i < n; i++) {
      int j = 0;
      for (int k = 0; k < __builtin_ctz(n); k++) {
        if (i & (1 << k)) j |= 1 << (__builtin_ctz(n) - 1 - k);
      }
      if (i < j) swap(a[i], a[j]);
    }
    for (int k = 1; k < n; k <<= 1) {
      Mint w = 1, r = pw[y - 1 - __builtin_ctz(k)];
      if (inverse) r = r.inverse();
      for (int i = 0; i < k; i++) {
        for (int j = 0; j < n; j += 2 * k) {
          Mint s = a[j + i], t = a[j + k + i] * w;
          a[j + i] = s + t, a[j + k + i] = s - t;
        }
        w *= r;
      }
    }
    if (inverse)
      for (int i = 0; i < n; i++) a[i] /= Mint(n);
  }
  vector<Mint> convolve(vector<Mint> a, vector<Mint> b) const {
    int n = 1 << (32 - __builtin_clz((int)a.size() + (int)b.size() - 2));
    int k = (int)a.size() + (int)b.size() - 1;
    vector<Mint> c(n);
    ntt(a, n), ntt(b, n);
    for (int i = 0; i < n; i++) c[i] = a[i] * b[i];
    ntt(c, n, true);
    c.resize(k);
    return c;
  }
};
int main() {
  int N;
  cin >> N;
  vector<int> cnt(100000, 0);
  for (int i = 0; i < 2 * N; i++) {
    int h;
    cin >> h;
    h--;
    cnt[h]++;
  }
  combination<mint> C(N);
  Number_Theorem_Transform<MOD, 3> NTT;
  mint dp[N + 1];
  dp[0] = 1;
  for (int i = 1; i <= N; i++) dp[i] = dp[i - 1] * (2 * i - 1);
  priority_queue<pii, vector<pii>, greater<pii>> que;
  vector<mint> a[100000];
  for (int i = 0; i < 100000; i++) {
    if (cnt[i] <= 1) continue;
    a[i].resize(1 + cnt[i] / 2);
    a[i][0] = 1;
    for (int j = 1; j <= cnt[i] / 2; j++) {
      a[i][j] = a[i][j - 1] * C.comb(cnt[i] - 2 * (j - 1), 2) / mint(j);
    }
    que.emplace((int)a[i].size(), i);
  }
  if ((int)que.size() == 0) {
    cout << dp[N] << endl;
    return 0;
  }
  while ((int)que.size() >= 2) {
    int i = que.top().second;
    que.pop();
    int j = que.top().second;
    que.pop();
    a[i] = NTT.convolve(a[i], a[j]);
    a[j].clear();
    que.emplace((int)a[i].size(), i);
  }
  mint ans = 0;
  for (int i = 0; i < 100000; i++) {
    if (a[i].empty()) continue;
    for (int j = 0; j < (int)a[i].size(); j++) {
      if (j & 1)
        ans -= dp[N - j] * a[i][j];
      else
        ans += dp[N - j] * a[i][j];
    }
  }
  cout << ans << endl;
}
