#include <iostream>
#include <iomanip>
#include <algorithm>
#include <array>
#include <cassert>
#include <optional>
#include <utility>
#include <vector>
#include <atcoder/all>

template <class InputIterator>
std::ostream& range_output(std::ostream& os_arg, InputIterator first_arg, InputIterator last_arg){ if(first_arg != last_arg){ do{ os_arg << *(first_arg++); if(first_arg == last_arg) break; os_arg << ' '; } while(true); }  return os_arg; }
template <class Tp> std::ostream& operator << (std::ostream& os_arg, const std::vector<Tp>& arr_arg){ return range_output(os_arg, arr_arg.cbegin(), arr_arg.cend()); }
template <class Tp, std::size_t Size> std::ostream& operator << (std::ostream& os_arg, const std::array<Tp, Size>& arr_arg){ return range_output(os_arg, arr_arg.cbegin(), arr_arg.cend()); }
template <class S, class T> std::ostream& operator << (std::ostream& os_arg, const std::pair<S, T>& pair_arg){ return os_arg << '(' << pair_arg.first << ", " << pair_arg.second << ')'; }

#ifndef ONLINE_JUDGE
    template <typename Head> void dump_out(Head head_arg){ std::cerr << head_arg << '\n'; }
    template <typename Head, typename... Tail>
    void dump_out(Head head_arg, Tail... tail_args){ std::cerr << head_arg << ", "; dump_out(tail_args...); }
    #define dump(...) do { std::cerr << "[in line " << __LINE__ << "] " << #__VA_ARGS__ << " : "; dump_out(__VA_ARGS__); } while(false)
#else
    #define dump(...) (void(0))
#endif

using modint = atcoder::modint998244353;

struct Compare_t{
    bool operator ()(const std::vector<modint>& A, const std::vector<modint>& B){
        if(A.size() != B.size()) return A.size() > B.size();
        return &A < &B;
    }
};



int main(void){
    std::cin.tie(nullptr); std::ios_base::sync_with_stdio(false); 
    std::cout << std::fixed << std::setprecision(16);
    int n; std::cin >> n; n *= 2;
    std::vector<int> A(n); for(auto& itr : A) std::cin >> itr;

    std::vector<modint> F(n + 1); F[0] = modint(1); for(int i = 1; i <= n; ++i) F[i] = F[i - 1] * modint::raw(i);
    std::vector<modint> iF(n + 1); iF[n] = F[n].inv();
    for(int i = n; i > 0; --i) iF[i - 1] = iF[i] * modint::raw(i);
    const auto combination = [&](const int arg_n, const int arg_k){
        if(arg_k < 0 or arg_k > arg_n) return modint(0);
        return F[arg_n] * iF[arg_k] * iF[arg_n - arg_k];
    };
    std::vector<modint> FF(n);
    FF[0] = 1; FF[1] = 1; for(int i = 2; i <= n; ++i) FF[i] = FF[i - 2] * modint::raw(i);
    const auto choose = [&](int t){
        if(t == 0) return modint(1);
        return FF[2 * t - 1];
    };

    std::sort(A.begin(), A.end());
    std::priority_queue<std::vector<modint>, std::vector<std::vector<modint>>, Compare_t> PQ;
    PQ.push(std::vector<modint>{1});
    for(int i = 0; i < n; ){
        int cnt = 1; while(i + cnt < n and A[i] == A[i + cnt]) ++cnt;
        std::vector<modint> V(cnt / 2 + 1);
        V[0] = 1;
        for(int idx = 1; 2 * idx <= cnt; ++idx){
            V[idx] = combination(cnt, 2 * idx) * choose(idx);
        }
        PQ.push(V);
        i += cnt;
    }
    while(PQ.size() > 1){
        const auto V1 = PQ.top(); PQ.pop();
        const auto V2 = PQ.top(); PQ.pop();
        PQ.push(atcoder::convolution(V1, V2));
    }
    const auto V = PQ.top();
    const int nn = n / 2;
    modint res(0);
    for(int i = 0; i < V.size(); ++i){
        if(i & 1) res -= V[i] * choose(nn - i);
        else res += V[i] * choose(nn - i);
    } std::cout << '\n';
    std::cout << res.val() << '\n';
    
    return 0;
}

Measured memory_bytes: 10248