Problem p02539 (C++)

Buggy submission (status: Time Limit Exceeded):
#include <bits/stdc++.h>
using namespace std;

template <typename T> struct Z_p {
  using Type = typename decay<decltype(T::value)>::type;
  static vector<Type> mod_inv;
  constexpr Z_p() : value() {}
  template <typename U> Z_p(const U &x) { value = normalize(x); }
  template <typename U> static Type normalize(const U &x) {
    Type v;
    if (-mod() <= x && x < mod())
      v = static_cast<Type>(x);
    else
      v = static_cast<Type>(x % mod());
    if (v < 0)
      v += mod();
    return v;
  }
  const Type &operator()() const { return value; }
  template <typename U> explicit operator U() const {
    return static_cast<U>(value);
  }
  constexpr static Type mod() { return T::value; }
  Z_p &operator+=(const Z_p &otr) {
    if ((value += otr.value) >= mod())
      value -= mod();
    return *this;
  }
  Z_p &operator-=(const Z_p &otr) {
    if ((value -= otr.value) < 0)
      value += mod();
    return *this;
  }
  template <typename U> Z_p &operator+=(const U &otr) {
    return *this += Z_p(otr);
  }
  template <typename U> Z_p &operator-=(const U &otr) {
    return *this -= Z_p(otr);
  }
  Z_p &operator++() { return *this += 1; }
  Z_p &operator--() { return *this -= 1; }
  Z_p operator++(int) {
    Z_p result(*this);
    *this += 1;
    return result;
  }
  Z_p operator--(int) {
    Z_p result(*this);
    *this -= 1;
    return result;
  }
  Z_p operator-() const { return Z_p(-value); }
  template <typename U = T>
  typename enable_if<is_same<typename Z_p<U>::Type, int>::value, Z_p>::type &
  operator*=(const Z_p &rhs) {
#ifdef _WIN32
    uint64_t x = static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value);
    uint32_t xh = static_cast<uint32_t>(x >> 32), xl = static_cast<uint32_t>(x),
             d, m;
    asm("divl %4; \n\t" : "=a"(d), "=d"(m) : "d"(xh), "a"(xl), "r"(mod()));
    value = m;
#else
    value = normalize(static_cast<int64_t>(value) *
                      static_cast<int64_t>(rhs.value));
#endif
    return *this;
  }
  template <typename U = T>
  typename enable_if<is_same<typename Z_p<U>::Type, int64_t>::value, Z_p>::type
      &
      operator*=(const Z_p &rhs) {
    int64_t q = static_cast<int64_t>(static_cast<long double>(value) *
                                     rhs.value / mod());
    value = normalize(value * rhs.value - q * mod());
    return *this;
  }
  template <typename U = T>
  typename enable_if<!is_integral<typename Z_p<U>::Type>::value, Z_p>::type &
  operator*=(const Z_p &rhs) {
    value = normalize(value * rhs.value);
    return *this;
  }
  Z_p operator^(long long e) const {
    Z_p b = *this, res = 1;
    if (e < 0)
      b = 1 / b, e = -e;
    for (; e; b *= b, e >>= 1)
      if (e & 1)
        res *= b;
    return res;
  }
  Z_p &operator^=(const long long &e) { return *this = *this ^ e; }
  Z_p &operator/=(const Z_p &otr) {
    Type a = otr.value, m = mod(), u = 0, v = 1;
    if (a < int(mod_inv.size()))
      return *this *= mod_inv[a];
    while (a) {
      Type t = m / a;
      m -= t * a;
      swap(a, m);
      u -= t * v;
      swap(u, v);
    }
    assert(m == 1);
    return *this *= u;
  }
  template <typename U> friend const Z_p<U> &abs(const Z_p<U> &v) { return v; }
  template <typename U>
  friend bool operator==(const Z_p<U> &lhs, const Z_p<U> &rhs);
  template <typename U>
  friend bool operator<(const Z_p<U> &lhs, const Z_p<U> &rhs);
  template <typename U> friend istream &operator>>(istream &in, Z_p<U> &number);
  Type value;
};
template <typename T> bool operator==(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return lhs.value == rhs.value;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator==(const Z_p<T> &lhs, U rhs) {
  return lhs == Z_p<T>(rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator==(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) == rhs;
}
template <typename T> bool operator!=(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return !(lhs == rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator!=(const Z_p<T> &lhs, U rhs) {
  return !(lhs == rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator!=(U lhs, const Z_p<T> &rhs) {
  return !(lhs == rhs);
}
template <typename T> bool operator<(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return lhs.value < rhs.value;
}
template <typename T> Z_p<T> operator+(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator+(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator+(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T> Z_p<T> operator-(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator-(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator-(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T> Z_p<T> operator*(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator*(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator*(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T> Z_p<T> operator/(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator/(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator/(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T> istream &operator>>(istream &in, Z_p<T> &number) {
  typename common_type<typename Z_p<T>::Type, int64_t>::type x;
  in >> x;
  number.value = Z_p<T>::normalize(x);
  return in;
}
template <typename T> ostream &operator<<(ostream &out, const Z_p<T> &number) {
  return out << number();
}

/*
using ModType = int;
struct VarMod{ static ModType value; };
ModType VarMod::value;
ModType &mod = VarMod::value;
using Zp = Z_p<VarMod>;
*/

constexpr int mod = (119 << 23) + 1;
using Zp = Z_p<integral_constant<decay<decltype(mod)>::type, mod>>;

template <typename T> vector<typename Z_p<T>::Type> Z_p<T>::mod_inv;
template <typename T = integral_constant<decay<decltype(mod)>::type, mod>>
void precalc_inverse(size_t SZ) {
  auto &inv = Z_p<T>::mod_inv;
  if (inv.empty())
    inv.assign(2, 1);
  for (; inv.size() <= SZ;)
    inv.push_back(
        (mod - 1LL * mod / int(inv.size()) * inv[mod % int(inv.size())] % mod) %
        mod);
}

// Requires Z_p
template <int SZ, typename T = Zp> struct combinatorics {
  vector<T> inv, fact, invfact;
  vector<vector<T>> stir1, stir2;
  combinatorics()
      : inv(SZ << 1 | 1, 1), fact(SZ << 1 | 1, 1), invfact(SZ << 1 | 1, 1) {
    for (int i = 1; i <= SZ << 1; ++i)
      fact[i] = fact[i - 1] * i;
    invfact[SZ << 1] = 1 / fact[SZ << 1];
    for (int i = (SZ << 1) - 1; i >= 0; --i) {
      invfact[i] = invfact[i + 1] * (i + 1);
      inv[i + 1] = invfact[i + 1] * fact[i];
    }
  } // O(SZ)
  T C(int n, int k) {
    return n < k ? 0 : fact[n] * invfact[k] * invfact[n - k];
  }
  T P(int n, int k) { return n < k ? 0 : fact[n] * invfact[n - k]; }
  T H(int n, int k) { return C(n + k - 1, k); }
  vector<T> precalc_power(int base, int n = SZ << 1) {
    vector<T> res(n + 1, 1);
    for (int i = 1; i <= n; ++i)
      res[i] = res[i - 1] * base;
    return res;
  }
  T naive_C(long long n, long long k) {
    if (n < k)
      return 0;
    T res = 1;
    k = min(k, n - k);
    for (long long i = n; i > n - k; --i)
      res *= i;
    return res * invfact[k];
  }
  T naive_P(long long n, int k) {
    if (n < k)
      return 0;
    T res = 1;
    for (long long i = n; i > n - k; --i)
      res *= i;
    return res;
  }
  T naive_H(long long n, long long k) { return naive_C(n + k - 1, k); }
  bool parity_C(long long n, long long k) { return n < k ? 0 : k & n - k ^ 1; }
  // Catalan's Trapzoids
  // # of bitstrings of n Xs and k Ys such that in each initial segment, (# of
  // X) + m > (# of Y)
  T Cat(int n, int k, int m = 1) {
    if (m <= 0)
      return 0;
    else if (k >= 0 && k < m)
      return C(n + k, k);
    else if (k < n + m)
      return C(n + k, k) - C(n + k, k - m);
    else
      return 0;
  }
  // Stirling number
  // First kind (unsigned): # of n-permutations with k disjoint cycles
  //                        Also the coefficient of x^k for x_n =
  //                        x(x+1)...(x+n-1)
  // Second kind: # of ways to partition a set of size n into r non-empty sets
  //              Satisfies sum{k=0~n}(x_k) = x^n
  array<bool, 2> pre{};
  template <bool FIRST = true> void precalc_stir(int n, int k) {
    auto &s = FIRST ? stir1 : stir2;
    pre[!FIRST] = true;
    s.resize(n + 1, vector<T>(k + 1, 1));
    for (int i = 1; i <= n; ++i)
      for (int j = 1; j <= k; ++j) {
        s[i][j] = (FIRST ? i - 1 : j) * s[i - 1][j] + s[i - 1][j - 1];
      }
  }
  // unsigned
  T Stir1(int n, int k) {
    if (n < k)
      return 0;
    assert(pre[0]);
    return stir1[n][k];
  }
  T Stir2(long long n, int k) {
    if (n < k)
      return 0;
    if (pre[1] && n < int(stir2.size()))
      return stir2[n][k];
    T res = 0;
    for (int i = 0, sign = 1; i <= k; ++i, sign *= -1)
      res += sign * C(k, i) * (Zp(k - i) ^ n);
    return res * invfact[k];
  }
  bool parity_Stir2(long long n, long long k) {
    return n < k ? 0 : k ? !(n - k & k - 1 >> 1) : 0;
  }
};

// Requires modular
template <int root = 15311432, int root_pw = 1 << 23, int inv_root = 469870224,
          typename IT = vector<Zp>::iterator>
void number_theoric_transform(IT begin, IT end, const bool invert = false) {
  int n = end - begin;
  for (int i = 1, j = 0; i < n; ++i) {
    int bit = n >> 1;
    for (; j & bit; bit >>= 1)
      j ^= bit;
    j ^= bit;
    if (i < j)
      swap(*(begin + i), *(begin + j));
  }
  for (int len = 1; len < n; len <<= 1) {
    typename iterator_traits<IT>::value_type wlen = invert ? inv_root : root;
    for (int i = len << 1; i < root_pw; i <<= 1)
      wlen *= wlen;
    for (int i = 0; i < n; i += len << 1) {
      typename iterator_traits<IT>::value_type w = 1;
      for (int j = 0; j < len; ++j) {
        auto u = *(begin + i + j), v = *(begin + i + j + len) * w;
        *(begin + i + j) = u + v;
        *(begin + i + j + len) = u - v;
        w *= wlen;
      }
    }
  }
  if (invert) {
    auto inv_n = typename iterator_traits<IT>::value_type(1) / n;
    for (auto it = begin; it != end; ++it)
      *it *= inv_n;
  }
}
const size_t magic_constant = 250;
template <typename Poly> void convolute(Poly &a, Poly b) {
  if (min(a.size(), b.size()) < magic_constant) {
    Poly res((int)a.size() + (int)b.size() - 1);
    for (size_t i = 0; i < a.size(); ++i)
      for (size_t j = 0; j < b.size(); ++j)
        res[i + j] += a[i] * b[j];
    a = move(res);
    return;
  }
  int n = max((int)a.size() + (int)b.size() - 1, 1);
  if (__builtin_popcount(n) != 1)
    n = 1 << __lg(n) + 1;
  a.resize(n), b.resize(n);
  number_theoric_transform(a.begin(), a.end()),
      number_theoric_transform(b.begin(), b.end());
  for (int i = 0; i < n; ++i)
    a[i] *= b[i];
  number_theoric_transform(a.begin(), a.end(), 1);
  while (!a.empty() && !a.back())
    a.pop_back();
}

// DEBUG BEGIN
#ifdef LOCAL
template <typename L, typename R>
ostream &operator<<(ostream &out, const pair<L, R> &p) {
  return out << "(" << p.first << ", " << p.second << ")";
}
template <typename Tuple, size_t N> struct TuplePrinter {
  static ostream &print(ostream &out, const Tuple &t) {
    return TuplePrinter<Tuple, N - 1>::print(out, t) << ", " << get<N - 1>(t);
  }
};
template <typename Tuple> struct TuplePrinter<Tuple, 1> {
  static ostream &print(ostream &out, const Tuple &t) {
    return out << get<0>(t);
  }
};
template <typename... Args>
ostream &print_tuple(ostream &out, const tuple<Args...> &t) {
  return TuplePrinter<decltype(t), sizeof...(Args)>::print(out << "(", t)
         << ")";
}
template <typename... Args>
ostream &operator<<(ostream &out, const tuple<Args...> &t) {
  return print_tuple(out, t);
}
template <typename T>
ostream &operator<<(enable_if_t<!is_same<T, string>::value, ostream> &out,
                    const T &arr) {
  out << "{";
  for (auto &x : arr)
    out << x << ", ";
  return out << (arr.size() ? "\b\b" : "") << "}";
}
template <size_t S> ostream &operator<<(ostream &out, const bitset<S> &b) {
  for (int i = 0; i < S; ++i)
    out << b[i];
  return out;
}
void debug_out() { cerr << "\b\b " << endl; }
template <typename Head, typename... Tail> void debug_out(Head H, Tail... T) {
  cerr << H << ", ", debug_out(T...);
}
void debug2_out() { cerr << "-----DEBUG END-----\n"; }
template <typename Head, typename... Tail> void debug2_out(Head H, Tail... T) {
  cerr << "\n";
  for (auto x : H)
    cerr << x << "\n";
  debug2_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__)
#define debug2(...)                                                            \
  cerr << "----DEBUG BEGIN----\n[" << #__VA_ARGS__ << "]:",                    \
      debug2_out(__VA_ARGS__)
#else
#define debug(...) 42
#define debug2(...) 42
#endif
// DEBUG END

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin.exceptions(ios::badbit | ios::failbit);
  combinatorics<100000> C;
  auto invp = C.precalc_power((Zp(1) / 2).value);
  int n;
  cin >> n;
  const int mx = 1e5;
  vector<int> cnt(mx);
  for (auto i = 0; i < 2 * n; ++i) {
    int x;
    cin >> x, --x;
    ++cnt[x];
  }
  auto calc = [&](int tot, int sub) {
    assert(2 * sub <= tot);
    return C.fact[tot] * C.invfact[sub] * invp[sub] * C.invfact[tot - 2 * sub];
  };
  vector<vector<Zp>> poly(mx);
  priority_queue<array<int, 2>, vector<array<int, 2>>, greater<>> pq;
  for (auto i = 0; i < mx; ++i) {
    if (cnt[i] > 1) {
      for (auto x = 0; x <= cnt[i] / 2; ++x) {
        poly[i].push_back(calc(cnt[i], x));
      }
      pq.push({(int)poly.size(), i});
    }
  }
  if (pq.empty()) {
    cout << calc(2 * n, n) << "\n";
    return 0;
  }
  while ((int)pq.size() > 1) {
    int i = pq.top()[1];
    pq.pop();
    int j = pq.top()[1];
    pq.pop();
    debug(i, j);
    convolute(poly[i], poly[j]);
    debug((int)poly[i].size(), i);
    pq.push({(int)poly[i].size(), i});
  }
  int i = pq.top()[1];
  Zp res = 0;
  for (auto j = 0, sign = 1; j < (int)poly[i].size(); ++j, sign = -sign) {
    res += sign * calc(2 * n - 2 * j, n - j) * poly[i][j];
  }
  cout << res << "\n";
  return 0;
}

/*

*/

////////////////////////////////////////////////////////////////////////////////////////
//                                                                                    //
//                                   Coded by Aeren //
//                                                                                    //
////////////////////////////////////////////////////////////////////////////////////////

Fixed submission:
#include <bits/stdc++.h>
using namespace std;

template <typename T> struct Z_p {
  using Type = typename decay<decltype(T::value)>::type;
  static vector<Type> mod_inv;
  constexpr Z_p() : value() {}
  template <typename U> Z_p(const U &x) { value = normalize(x); }
  template <typename U> static Type normalize(const U &x) {
    Type v;
    if (-mod() <= x && x < mod())
      v = static_cast<Type>(x);
    else
      v = static_cast<Type>(x % mod());
    if (v < 0)
      v += mod();
    return v;
  }
  const Type &operator()() const { return value; }
  template <typename U> explicit operator U() const {
    return static_cast<U>(value);
  }
  constexpr static Type mod() { return T::value; }
  Z_p &operator+=(const Z_p &otr) {
    if ((value += otr.value) >= mod())
      value -= mod();
    return *this;
  }
  Z_p &operator-=(const Z_p &otr) {
    if ((value -= otr.value) < 0)
      value += mod();
    return *this;
  }
  template <typename U> Z_p &operator+=(const U &otr) {
    return *this += Z_p(otr);
  }
  template <typename U> Z_p &operator-=(const U &otr) {
    return *this -= Z_p(otr);
  }
  Z_p &operator++() { return *this += 1; }
  Z_p &operator--() { return *this -= 1; }
  Z_p operator++(int) {
    Z_p result(*this);
    *this += 1;
    return result;
  }
  Z_p operator--(int) {
    Z_p result(*this);
    *this -= 1;
    return result;
  }
  Z_p operator-() const { return Z_p(-value); }
  template <typename U = T>
  typename enable_if<is_same<typename Z_p<U>::Type, int>::value, Z_p>::type &
  operator*=(const Z_p &rhs) {
#ifdef _WIN32
    uint64_t x = static_cast<int64_t>(value) * static_cast<int64_t>(rhs.value);
    uint32_t xh = static_cast<uint32_t>(x >> 32), xl = static_cast<uint32_t>(x),
             d, m;
    asm("divl %4; \n\t" : "=a"(d), "=d"(m) : "d"(xh), "a"(xl), "r"(mod()));
    value = m;
#else
    value = normalize(static_cast<int64_t>(value) *
                      static_cast<int64_t>(rhs.value));
#endif
    return *this;
  }
  template <typename U = T>
  typename enable_if<is_same<typename Z_p<U>::Type, int64_t>::value, Z_p>::type
      &
      operator*=(const Z_p &rhs) {
    int64_t q = static_cast<int64_t>(static_cast<long double>(value) *
                                     rhs.value / mod());
    value = normalize(value * rhs.value - q * mod());
    return *this;
  }
  template <typename U = T>
  typename enable_if<!is_integral<typename Z_p<U>::Type>::value, Z_p>::type &
  operator*=(const Z_p &rhs) {
    value = normalize(value * rhs.value);
    return *this;
  }
  Z_p operator^(long long e) const {
    Z_p b = *this, res = 1;
    if (e < 0)
      b = 1 / b, e = -e;
    for (; e; b *= b, e >>= 1)
      if (e & 1)
        res *= b;
    return res;
  }
  Z_p &operator^=(const long long &e) { return *this = *this ^ e; }
  Z_p &operator/=(const Z_p &otr) {
    Type a = otr.value, m = mod(), u = 0, v = 1;
    if (a < int(mod_inv.size()))
      return *this *= mod_inv[a];
    while (a) {
      Type t = m / a;
      m -= t * a;
      swap(a, m);
      u -= t * v;
      swap(u, v);
    }
    assert(m == 1);
    return *this *= u;
  }
  template <typename U> friend const Z_p<U> &abs(const Z_p<U> &v) { return v; }
  template <typename U>
  friend bool operator==(const Z_p<U> &lhs, const Z_p<U> &rhs);
  template <typename U>
  friend bool operator<(const Z_p<U> &lhs, const Z_p<U> &rhs);
  template <typename U> friend istream &operator>>(istream &in, Z_p<U> &number);
  Type value;
};
template <typename T> bool operator==(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return lhs.value == rhs.value;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator==(const Z_p<T> &lhs, U rhs) {
  return lhs == Z_p<T>(rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator==(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) == rhs;
}
template <typename T> bool operator!=(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return !(lhs == rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator!=(const Z_p<T> &lhs, U rhs) {
  return !(lhs == rhs);
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
bool operator!=(U lhs, const Z_p<T> &rhs) {
  return !(lhs == rhs);
}
template <typename T> bool operator<(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return lhs.value < rhs.value;
}
template <typename T> Z_p<T> operator+(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator+(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator+(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) += rhs;
}
template <typename T> Z_p<T> operator-(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator-(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator-(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) -= rhs;
}
template <typename T> Z_p<T> operator*(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator*(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator*(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) *= rhs;
}
template <typename T> Z_p<T> operator/(const Z_p<T> &lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator/(const Z_p<T> &lhs, U rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T, typename U,
          typename enable_if<is_integral<U>::value>::type * = nullptr>
Z_p<T> operator/(U lhs, const Z_p<T> &rhs) {
  return Z_p<T>(lhs) /= rhs;
}
template <typename T> istream &operator>>(istream &in, Z_p<T> &number) {
  typename common_type<typename Z_p<T>::Type, int64_t>::type x;
  in >> x;
  number.value = Z_p<T>::normalize(x);
  return in;
}
template <typename T> ostream &operator<<(ostream &out, const Z_p<T> &number) {
  return out << number();
}

/*
using ModType = int;
struct VarMod{ static ModType value; };
ModType VarMod::value;
ModType &mod = VarMod::value;
using Zp = Z_p<VarMod>;
*/

constexpr int mod = (119 << 23) + 1;
using Zp = Z_p<integral_constant<decay<decltype(mod)>::type, mod>>;

template <typename T> vector<typename Z_p<T>::Type> Z_p<T>::mod_inv;
template <typename T = integral_constant<decay<decltype(mod)>::type, mod>>
void precalc_inverse(size_t SZ) {
  auto &inv = Z_p<T>::mod_inv;
  if (inv.empty())
    inv.assign(2, 1);
  for (; inv.size() <= SZ;)
    inv.push_back(
        (mod - 1LL * mod / int(inv.size()) * inv[mod % int(inv.size())] % mod) %
        mod);
}

// Requires Z_p
template <int SZ, typename T = Zp> struct combinatorics {
  vector<T> inv, fact, invfact;
  vector<vector<T>> stir1, stir2;
  combinatorics()
      : inv(SZ << 1 | 1, 1), fact(SZ << 1 | 1, 1), invfact(SZ << 1 | 1, 1) {
    for (int i = 1; i <= SZ << 1; ++i)
      fact[i] = fact[i - 1] * i;
    invfact[SZ << 1] = 1 / fact[SZ << 1];
    for (int i = (SZ << 1) - 1; i >= 0; --i) {
      invfact[i] = invfact[i + 1] * (i + 1);
      inv[i + 1] = invfact[i + 1] * fact[i];
    }
  } // O(SZ)
  T C(int n, int k) {
    return n < k ? 0 : fact[n] * invfact[k] * invfact[n - k];
  }
  T P(int n, int k) { return n < k ? 0 : fact[n] * invfact[n - k]; }
  T H(int n, int k) { return C(n + k - 1, k); }
  vector<T> precalc_power(int base, int n = SZ << 1) {
    vector<T> res(n + 1, 1);
    for (int i = 1; i <= n; ++i)
      res[i] = res[i - 1] * base;
    return res;
  }
  T naive_C(long long n, long long k) {
    if (n < k)
      return 0;
    T res = 1;
    k = min(k, n - k);
    for (long long i = n; i > n - k; --i)
      res *= i;
    return res * invfact[k];
  }
  T naive_P(long long n, int k) {
    if (n < k)
      return 0;
    T res = 1;
    for (long long i = n; i > n - k; --i)
      res *= i;
    return res;
  }
  T naive_H(long long n, long long k) { return naive_C(n + k - 1, k); }
  bool parity_C(long long n, long long k) { return n < k ? 0 : k & n - k ^ 1; }
  // Catalan's Trapzoids
  // # of bitstrings of n Xs and k Ys such that in each initial segment, (# of
  // X) + m > (# of Y)
  T Cat(int n, int k, int m = 1) {
    if (m <= 0)
      return 0;
    else if (k >= 0 && k < m)
      return C(n + k, k);
    else if (k < n + m)
      return C(n + k, k) - C(n + k, k - m);
    else
      return 0;
  }
  // Stirling number
  // First kind (unsigned): # of n-permutations with k disjoint cycles
  //                        Also the coefficient of x^k for x_n =
  //                        x(x+1)...(x+n-1)
  // Second kind: # of ways to partition a set of size n into r non-empty sets
  //              Satisfies sum{k=0~n}(x_k) = x^n
  array<bool, 2> pre{};
  template <bool FIRST = true> void precalc_stir(int n, int k) {
    auto &s = FIRST ? stir1 : stir2;
    pre[!FIRST] = true;
    s.resize(n + 1, vector<T>(k + 1, 1));
    for (int i = 1; i <= n; ++i)
      for (int j = 1; j <= k; ++j) {
        s[i][j] = (FIRST ? i - 1 : j) * s[i - 1][j] + s[i - 1][j - 1];
      }
  }
  // unsigned
  T Stir1(int n, int k) {
    if (n < k)
      return 0;
    assert(pre[0]);
    return stir1[n][k];
  }
  T Stir2(long long n, int k) {
    if (n < k)
      return 0;
    if (pre[1] && n < int(stir2.size()))
      return stir2[n][k];
    T res = 0;
    for (int i = 0, sign = 1; i <= k; ++i, sign *= -1)
      res += sign * C(k, i) * (Zp(k - i) ^ n);
    return res * invfact[k];
  }
  bool parity_Stir2(long long n, long long k) {
    return n < k ? 0 : k ? !(n - k & k - 1 >> 1) : 0;
  }
};

// Requires modular
template <int root = 15311432, int root_pw = 1 << 23, int inv_root = 469870224,
          typename IT = vector<Zp>::iterator>
void number_theoric_transform(IT begin, IT end, const bool invert = false) {
  int n = end - begin;
  for (int i = 1, j = 0; i < n; ++i) {
    int bit = n >> 1;
    for (; j & bit; bit >>= 1)
      j ^= bit;
    j ^= bit;
    if (i < j)
      swap(*(begin + i), *(begin + j));
  }
  for (int len = 1; len < n; len <<= 1) {
    typename iterator_traits<IT>::value_type wlen = invert ? inv_root : root;
    for (int i = len << 1; i < root_pw; i <<= 1)
      wlen *= wlen;
    for (int i = 0; i < n; i += len << 1) {
      typename iterator_traits<IT>::value_type w = 1;
      for (int j = 0; j < len; ++j) {
        auto u = *(begin + i + j), v = *(begin + i + j + len) * w;
        *(begin + i + j) = u + v;
        *(begin + i + j + len) = u - v;
        w *= wlen;
      }
    }
  }
  if (invert) {
    auto inv_n = typename iterator_traits<IT>::value_type(1) / n;
    for (auto it = begin; it != end; ++it)
      *it *= inv_n;
  }
}
const size_t magic_constant = 250;
template <typename Poly> void convolute(Poly &a, Poly b) {
  if (min(a.size(), b.size()) < magic_constant) {
    Poly res((int)a.size() + (int)b.size() - 1);
    for (size_t i = 0; i < a.size(); ++i)
      for (size_t j = 0; j < b.size(); ++j)
        res[i + j] += a[i] * b[j];
    a = move(res);
    return;
  }
  int n = max((int)a.size() + (int)b.size() - 1, 1);
  if (__builtin_popcount(n) != 1)
    n = 1 << __lg(n) + 1;
  a.resize(n), b.resize(n);
  number_theoric_transform(a.begin(), a.end()),
      number_theoric_transform(b.begin(), b.end());
  for (int i = 0; i < n; ++i)
    a[i] *= b[i];
  number_theoric_transform(a.begin(), a.end(), 1);
  while (!a.empty() && !a.back())
    a.pop_back();
}

// DEBUG BEGIN
#ifdef LOCAL
template <typename L, typename R>
ostream &operator<<(ostream &out, const pair<L, R> &p) {
  return out << "(" << p.first << ", " << p.second << ")";
}
template <typename Tuple, size_t N> struct TuplePrinter {
  static ostream &print(ostream &out, const Tuple &t) {
    return TuplePrinter<Tuple, N - 1>::print(out, t) << ", " << get<N - 1>(t);
  }
};
template <typename Tuple> struct TuplePrinter<Tuple, 1> {
  static ostream &print(ostream &out, const Tuple &t) {
    return out << get<0>(t);
  }
};
template <typename... Args>
ostream &print_tuple(ostream &out, const tuple<Args...> &t) {
  return TuplePrinter<decltype(t), sizeof...(Args)>::print(out << "(", t)
         << ")";
}
template <typename... Args>
ostream &operator<<(ostream &out, const tuple<Args...> &t) {
  return print_tuple(out, t);
}
template <typename T>
ostream &operator<<(enable_if_t<!is_same<T, string>::value, ostream> &out,
                    const T &arr) {
  out << "{";
  for (auto &x : arr)
    out << x << ", ";
  return out << (arr.size() ? "\b\b" : "") << "}";
}
template <size_t S> ostream &operator<<(ostream &out, const bitset<S> &b) {
  for (int i = 0; i < S; ++i)
    out << b[i];
  return out;
}
void debug_out() { cerr << "\b\b " << endl; }
template <typename Head, typename... Tail> void debug_out(Head H, Tail... T) {
  cerr << H << ", ", debug_out(T...);
}
void debug2_out() { cerr << "-----DEBUG END-----\n"; }
template <typename Head, typename... Tail> void debug2_out(Head H, Tail... T) {
  cerr << "\n";
  for (auto x : H)
    cerr << x << "\n";
  debug2_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__)
#define debug2(...)                                                            \
  cerr << "----DEBUG BEGIN----\n[" << #__VA_ARGS__ << "]:",                    \
      debug2_out(__VA_ARGS__)
#else
#define debug(...) 42
#define debug2(...) 42
#endif
// DEBUG END

int main() {
  cin.tie(0)->sync_with_stdio(0);
  cin.exceptions(ios::badbit | ios::failbit);
  combinatorics<100000> C;
  auto invp = C.precalc_power((Zp(1) / 2).value);
  int n;
  cin >> n;
  const int mx = 1e5;
  vector<int> cnt(mx);
  for (auto i = 0; i < 2 * n; ++i) {
    int x;
    cin >> x, --x;
    ++cnt[x];
  }
  auto calc = [&](int tot, int sub) {
    assert(2 * sub <= tot);
    return C.fact[tot] * C.invfact[sub] * invp[sub] * C.invfact[tot - 2 * sub];
  };
  vector<vector<Zp>> poly(mx);
  priority_queue<array<int, 2>, vector<array<int, 2>>, greater<>> pq;
  for (auto i = 0; i < mx; ++i) {
    if (cnt[i] > 1) {
      for (auto x = 0; x <= cnt[i] / 2; ++x) {
        poly[i].push_back(calc(cnt[i], x));
      }
      pq.push({(int)poly[i].size(), i});
    }
  }
  if (pq.empty()) {
    cout << calc(2 * n, n) << "\n";
    return 0;
  }
  while ((int)pq.size() > 1) {
    int i = pq.top()[1];
    pq.pop();
    int j = pq.top()[1];
    pq.pop();
    debug(i, j);
    convolute(poly[i], poly[j]);
    debug((int)poly[i].size(), i);
    pq.push({(int)poly[i].size(), i});
  }
  int i = pq.top()[1];
  Zp res = 0;
  for (auto j = 0, sign = 1; j < (int)poly[i].size(); ++j, sign = -sign) {
    res += sign * calc(2 * n - 2 * j, n - j) * poly[i][j];
  }
  cout << res << "\n";
  return 0;
}

/*

*/

////////////////////////////////////////////////////////////////////////////////////////
//                                                                                    //
//                                   Coded by Aeren //
//                                                                                    //
////////////////////////////////////////////////////////////////////////////////////////