/*/ Author: kal013 /*/
// #pragma GCC optimize ("O3")
#include "bits/stdc++.h"
#include "ext/pb_ds/assoc_container.hpp"
#include "ext/pb_ds/tree_policy.hpp"
using namespace std;
using namespace __gnu_pbds;

template<class T> 
using ordered_set = tree<T, null_type,less<T>, rb_tree_tag,tree_order_statistics_node_update> ;

template<class key, class value, class cmp = std::less<key>>
using ordered_map = tree<key, value, cmp, rb_tree_tag, tree_order_statistics_node_update>;
// find_by_order(k)  returns iterator to kth element starting from 0;
// order_of_key(k) returns count of elements strictly smaller than k;

template<class T>
using min_heap = priority_queue<T,vector<T>,greater<T> >; 

struct custom_hash { // Credits: https://codeforces.com/blog/entry/62393
    static uint64_t splitmix64(uint64_t x) {
        // http://xorshift.di.unimi.it/splitmix64.c
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }

    size_t operator()(uint64_t x) const {
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
    size_t operator()(pair<int64_t,int64_t> Y) const{
        static const uint64_t FIXED_RANDOM = chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(Y.first * 31 + Y.second + FIXED_RANDOM);
    }
};

template<class T> ostream& operator<<(ostream &os, vector<T> V) {
    os << "[ ";
    for(auto v : V) os << v << " ";
    return os << "]";
}
template<class T> istream& operator>>(istream &is, vector<T>& V) {
    for(auto &e : V)
        is >> e;
    return is;
}

#ifdef __SIZEOF_INT128__
ostream& operator << (ostream &os, __int128 T){
    const static long long N = (1'000'000'000'000'000'000);
    if (T < 0){
        os << '-';
        T *= -1;
    }

    unsigned long long b = T%N;
    T /= N;
    if (T == 0)
        return os << b ;
    unsigned long long a = T % N;
    T /= N;
    if (T == 0){
        os << a;
        __int128 mul = 10;
        int cnt = 0;
        while( mul*b < N && cnt < 17){
            os << '0';
            mul *= 10;
            ++cnt;
        }
        return os << b;
    }
    os << ((long long) T);
    
     __int128 mul = 10;
      int cnt = 0;
    while( mul*a < N && cnt < 17){
        os << '0';
        mul *= 10;
        ++cnt;
    }
    os << a;
    mul = 10;
    cnt = 0;
    while( mul*b < N && cnt < 17){
        os << '0';
        mul *= 10;
        ++cnt;
    }
    return os << b;

}
istream& operator >> (istream& is, __int128& T){
    string U;
    is >> U;
    T = 0;
    size_t pos = 0;
    int mul = 1;
    if (U[pos] == '-'){
        ++pos;
        mul *= -1;
    }
    for(; pos < U.size(); ++pos){
        T *= 10;
        T += (U[pos] - '0');
    }
    T *= mul;

    return is;
}
#endif
template<class T> ostream& operator<<(ostream &os, set<T> S){
    os << "{ ";
    for(auto s:S) os<<s<<" ";
    return os<<"}";
}
template<class T> ostream& operator<<(ostream &os, unordered_set<T> S){
    os << "{ ";
    for(auto s:S) os<<s<<" ";
    return os<<"}";
}
template<class T> ostream& operator << (ostream& os, multiset<T> S){
    os << "{ ";
    for(auto s:S) os<<s<<" ";
    return os<<"}";
}
template<class T> ostream& operator<<(ostream &os, ordered_set<T> S){
    os << "{ ";
    for(auto s:S) os<<s<<" ";
    return os<<"}";
}
template<class L, class R> ostream& operator<<(ostream &os, pair<L,R> P) {
    return os << "(" << P.first << "," << P.second << ")";
}
template<class L, class R> ostream& operator<<(ostream &os, map<L,R> M) {
    os << "{ ";
    for(auto m:M) os<<"("<<m.first<<":"<<m.second<<") ";
    return os<<"}";
}
template<class L, class R, class chash = std::hash<R>> ostream& operator<<(ostream &os, unordered_map<L,R,chash> M) {
    os << "{ ";
    for(auto m:M) os<<"("<<m.first<<":"<<m.second<<") ";
    return os<<"}";
}
template<class L, class R, class chash = std::hash<R> > ostream& operator<<(ostream &os, gp_hash_table<L,R,chash> M) {
    os << "{ ";
    for(auto m:M) os<<"("<<m.first<<":"<<m.second<<") ";
    return os<<"}";
}

#define TRACE
#ifdef TRACE
    #define trace(...) __f(#__VA_ARGS__, __VA_ARGS__)
    template <typename Arg1>
    void __f(const char* name, Arg1&& arg1){
        cerr << name << " : " << arg1 << endl;
    }
    template <typename Arg1, typename... Args>
    void __f(const char* names, Arg1&& arg1, Args&&... args){
        const char* comma = strchr(names + 1, ',');
        cerr.write(names, comma - names) << " : " << arg1<<" | ";
        __f(comma+1, args...);
    }
#else
    #define trace(...) 1
#endif

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());

inline int64_t random_long(long long l,long long r){
    uniform_int_distribution<int64_t> generator(l,r);
    return generator(rng);
}
inline int64_t random_long(){
    uniform_int_distribution<int64_t> generator(LLONG_MIN,LLONG_MAX);
    return generator(rng);
}


/*/---------------------------Defines----------------------/*/
typedef vector<int> vi;
typedef pair<int,int> pii;

#define For(i,n) for(int i = 0; i < (int) n; ++i)
#define Rep(i,n) for(int i = 1; i <= (int) n; ++i)
#define ll long long
#define F first
#define S second
#define pb push_back
#define endl "\n"
#define all(v) (v).begin(),(v).end()
/*/-----------------------Modular Arithmetic---------------/*/

const int Mod = 998244353;
const int prim_root = 3;
const int mod = Mod; //make add,sub,mul,power,inv functions. 
template<const int MOD>
struct modular_int{
	int x;
	static vector<int> inverse_list ;
	const static int inverse_limit;
	const static bool is_prime;
	modular_int(){
		x = 0;
	}
	template<typename T>  
	modular_int(const T z){
		x = (z%MOD);
		if (x < 0) x += MOD; 	
	}
	modular_int(const modular_int<MOD>* z){
		x = z->x;
	}
	modular_int(const modular_int<MOD>& z){
		x = z.x;
	}
	modular_int operator - (const modular_int<MOD>& m) const{
		modular_int<MOD> U;
		U.x = x - m.x;
		if (U.x < 0) U.x += MOD;
		return U;
	}
	modular_int operator + (const modular_int<MOD>& m) const{
		modular_int<MOD> U;
		U.x = x + m.x;
		if (U.x >= MOD) U.x -= MOD;
		return U;
	}
	modular_int& operator -= (const modular_int<MOD>& m){
		x -= m.x;
		if (x < 0) x += MOD;
		return *this;
	}	
	modular_int& operator += (const modular_int<MOD>& m){
		x += m.x;
		if (x >= MOD) x -= MOD;
		return *this;
	}	
	modular_int operator * (const modular_int<MOD>& m) const{
		modular_int<MOD> U;
		U.x = (x* 1ull * m.x) %MOD;
		return U;
	}
	modular_int& operator *= (const modular_int<MOD>& m){
		x = (x * 1ull * m.x)%MOD;
		return *this;
	}
	template<typename T>
	friend modular_int operator + (const T &l, const modular_int<MOD>& p){
		return (modular_int<MOD>(l) + p);
	}
	template<typename T>
	friend modular_int operator - (const T &l, const modular_int<MOD>& p){
		return (modular_int<MOD>(l) - p);
	}
	template<typename T>
	friend modular_int operator * (const T &l, const modular_int<MOD>& p){
		return (modular_int<MOD>(l) * p);
	}
	template<typename T>
	friend modular_int operator / (const T &l, const modular_int<MOD>& p){
		return (modular_int<MOD>(l) / p);
	}

	int value() const{
		return x;
	}

	modular_int operator ^ (const modular_int<MOD>& cpower) const{
		modular_int<MOD> ans;
		ans.x = 1;
		modular_int<MOD> curr(this);
		int power = cpower.x;

		if (curr.x <= 1){
			if (power == 0) curr.x = 1;
			return curr;
		}
		while( power > 0){
			if (power&1){
				ans *= curr;
			}
			power >>= 1;
			if (power) curr *= curr;
		}
		return ans;
	}


	modular_int operator ^ (long long power) const{
		modular_int<MOD> ans;
		ans.x = 1;
		modular_int<MOD> curr(this);
		if (curr.x <= 1){
			if (power == 0) curr.x = 1;
			return curr;
		}
		// Prime Mods
		if (power >= MOD && is_prime){
			power %= (MOD - 1);
		}

		while( power > 0){
			if (power&1){
				ans *= curr;
			}
			power >>= 1;
			if (power) curr *= curr;

		}
		return ans;
	}

	modular_int operator ^ (int power) const{
		modular_int<MOD> ans;
		ans.x = 1;
		modular_int<MOD> curr(this);
		
		if (curr.x <= 1){
			if (power == 0) curr.x = 1;
			return curr;
		}
		while( power > 0){
			if (power&1){
				ans *= curr;
			}
			power >>= 1;
			if (power) curr *= curr;

		}
		return ans;
	}
	
	template<typename T>
	modular_int& operator ^= (T power) {
		modular_int<MOD> res = (*this)^power;
		x = res.x;
		return *this;
	}


	template<typename T>
	modular_int pow(T x){
		return (*this)^x;
	}

	
	pair<long long,long long> gcd(const int a, const int b) const {
		if (b==0) return {1,0}; 
		pair<long long,long long> c = gcd(b , a%b);
		return { c.second , c.first - (a/b)*c.second}; 
	}

	inline void init_inverse_list() const {
		
		vector<int>& dp = modular_int<MOD>::inverse_list;
		dp.resize(modular_int<MOD>::inverse_limit + 1);
		int n = modular_int<MOD>::inverse_limit;
		dp[0] = 1;
		if (n > 1) dp[1] = 1;
		for(int i = 2; i <= n; ++i){
			dp[i] = (dp[MOD%i] * 1ull *(MOD - MOD/i))%MOD;
		}
	
	}
	modular_int<MOD> get_inv() const {
		if (modular_int<MOD>::inverse_list.size() < modular_int<MOD>::inverse_limit + 1) init_inverse_list();

		if (this->x <= modular_int<MOD>::inverse_limit){
			return modular_int<MOD>::inverse_list[this->x];
		}
		pair<long long,long long> G = gcd(this->x,MOD);
		return modular_int<MOD>(G.first);
	}
	modular_int<MOD> operator - () const {
		modular_int<MOD> v(0);
		v -= (*this);
		return v;
	}
	modular_int operator / (const modular_int<MOD>& m) const{
		modular_int<MOD> U(this);
		U *= m.get_inv();
		return U;
	}
	modular_int& operator /= (const modular_int<MOD>& m){
		(*this) *= m.get_inv();
		return *this;
	}
	bool operator==(const modular_int<MOD>& m) const{
		return x == m.x;
	}

	bool operator < (const modular_int<MOD>& m) const {
		return x < m.x;
	}


	template<typename T>
	bool operator == (const T& m) const{
		return (*this)== (modular_int<MOD>(m));
	}

	template<typename T>
	bool operator < (const T& m) const {
		return x < (modular_int<MOD>(m)).x;
	}
	template<typename T>
	bool operator > (const T& m) const {
		return x > (modular_int<MOD>(m)).x;
	}
	template<typename T>
	friend bool operator == (const T& x, const modular_int<MOD>& m) {
		return (modular_int<MOD>(x)).x == m.x;
	}
	template<typename T> 
	friend bool operator < (const T& x, const modular_int<MOD>& m){
		return (modular_int<MOD>(x)).x < m.x;
	}
	template<typename T> 
	friend bool operator > (const T& x, const modular_int<MOD>& m){
		return (modular_int<MOD>(x)).x > m.x;
	}

	friend istream& operator >> (istream& is, modular_int<MOD>& p){
		int64_t val;
		is >> val;
		p.x = (val%MOD);
		if (p.x < 0) p.x += MOD;
		return is;
	}
	friend ostream& operator << (ostream& os, const modular_int<MOD>& p) {return os<<p.x;}



};



using mint = modular_int<mod>;
template<const int MOD>
vector<int> modular_int<MOD>::inverse_list ;
template<const int MOD>
const int modular_int<MOD>::inverse_limit = -1;
template<const int MOD>
const bool modular_int<MOD>::is_prime = true;
template<>   //-> useful if computing inverse fact = i_f[i-1] / i;
const int modular_int<mod>::inverse_limit = 100000;


inline int add(int x, int y){
	x += y;
	if (x >= mod){
		return x - mod;
	}
	return x;
}

inline int sub(int x,int y){
	x -= y;
	if (x < 0)
		return x + mod;
	return x;
}

inline int mul(int x,int y){
	return (x * 1ll * y) % mod;
}

inline int power(int x, int y){
	int ans = 1;
	while(y > 0){
		if (y & 1){
			ans = mul(ans, x);
		}
		x = mul(x, x);
		y >>= 1;
	}
	return ans;
}
inline int inv(int x){
	return power(x, mod - 2);
}
int base=1; vi roots = {0, 1}, rev = {0, 1};
int max_base=17; //x such that 2^x|(mod-1) & 2^x>max answer size(=2*n)
int root=202376916; //primitive root^((mod-1)/(2^max_base))
void ensure_base(int nbase) {
	if (nbase <= base) {return; }
	assert(nbase <= max_base);rev.resize(1<<nbase);
	for (int i=0; i<(1 << nbase); i++){
	  rev[i] = (rev[i >> 1] >> 1) + ((i & 1) << (nbase - 1)); }
	roots.resize(1 << nbase); 
	while (base < nbase) {
	  int z=power(root,1<<(max_base - 1 - base));
	  for (int i = 1 << (base - 1); i < (1 << base); i++) {
	    roots[(i << 1) | 1] = roots[i];
	    roots[(i << 1)] = mul(roots[i], z);
	  } base++; } }
void fft(vector<int> &a, bool invert=0) {
	int n=(int)a.size(); assert((n&(n - 1))==0);
	int zeros=__builtin_ctz(n); ensure_base(zeros);
	int shift = base-zeros;
	for (int i = 0; i < n; i++) {
	  if (i < (rev[i] >> shift)) 
	  	swap(a[i], a[rev[i] >> shift]); }
	for (int k = 1; k < n; k <<= 1) {
	  for (int i = 0; i < n; i += 2 * k) {
	    for (int j = 0; j < k; j++) {
	      int x = a[i + j];
	      int y = mul(a[i + j + k], (invert? inv(roots[j+k]):roots[j+k] ));
	      a[i + j] = x + y - mod;
	      if (a[i + j] < 0) a[i + j] += mod;
	      a[i + j + k] = x - y + mod;
	      if(a[i + j + k] >= mod)a[i+j+k] -= mod;
	}}}
	int inn=inv(n); 
	if(invert)for(auto &z:a)z=mul(z,inn); }
vector<int> multiply(vector<int> a, vector<int> b, int eq = 0) {
	int need=(int)(a.size()+b.size())-1; 
	int nbase=0; while((1 << nbase)<need) nbase++;
	ensure_base(nbase); int sz = 1 << nbase;
	a.resize(sz); b.resize(sz); fft(a);
	if (eq) b = a; else fft(b);
	int inv_sz = inv(sz);
	for (int i = 0; i < sz; i++) {
	  a[i] = mul(mul(a[i], b[i]) , inv_sz); }
	reverse(a.begin(), a.end()); fft(a, 0);
	a.insert(a.begin(), a.back());
	a.resize(need); return a; }	
vector<int> square(vector<int> a) {
	return multiply(a, a, 1); }
void init(){
	base=1; rev={0,1}; roots={0,1};
	root =power(prim_root,((mod-1)/(1<<max_base)));
	roots[1] = power(root, 1<<(max_base-1)); }


/*/-----------------------------Code begins----------------------------------/*/
const int N = 2e5 + 10;
int cnt[N];

mint fact[N], i_f[N];

mint dp[N];
void preprocess(){
	fact[0] = 1;
	i_f[0] = 1;
	dp[0] = 1;
	for(int i = 1; i < N; ++i){
		fact[i] = fact[i - 1] * i;
		i_f[i] = i_f[i - 1] / i;
		if (~i & 1)
			dp[i] = dp[i - 2] * (i - 1);
	}
}

mint ncr(int n,int r){
	if (r < 0 || r > n)
		return 0;
	return fact[n] * i_f[r] * i_f[n - r];
}

mint ways(int y,int k){
	if (2 * k > y){
		return 0;
	}

	return ncr(y, 2 * k) * dp[2 * k];
}

vector<int> adj[4 * N];


void make_vec(vector<int>& A, int y){
	A.clear();
	for(int k = 0; 2 * k <= y; ++k){
		mint res = ways(y, k);
		A.push_back(res.value());
	}
}

int merge(int x,int y){
	assert(x != y);
	vector<int> cur = multiply(adj[x], adj[y]);
	if (adj[x].size() < adj[y].size()){
		swap(x, y);
	}
	adj[y].clear();
	adj[x].clear();
	for(auto e: cur){
		adj[x].push_back(e);
	}
	return x;
}
void solve(){
	int n;
	cin >> n;

	for(int i = 0; i < 2 * n; ++i){
		int h;
		cin >> h;
		++cnt[h];
	}
	vector<int> values;
	for(int i = 0; i < N; ++i){
		if (cnt[i] <= 1){
			continue;
		}
		values.push_back(cnt[i]);
	}
	if (values.size() == 0){
		cout << dp[2 * n] << endl;
		return;
	}
	sort(all(values));
	set<pair<int,int>> tot;

	for(int i = 0; i < values.size(); ++i){
		make_vec(adj[i], values[i]);
		tot.insert({adj[i].size(), i});
	}

	while(tot.size() > 1){
		auto X = *tot.begin();
		tot.erase(X);
		auto Y = *tot.begin();
		tot.erase(Y);
		int pos = merge(X.second, Y.second);
		tot.insert({adj[pos].size(), pos});
	}

	int pos = (*tot.begin()).second;


	mint ans = 0;
	assert(adj[pos].size() <= n + 1);
	mint mul = 1;
	for(int k = 0; k <= n && k < adj[pos].size(); ++k){
		mint cur = dp[2 * (n - k)];
		cur *= adj[pos][k];
		ans += cur * mul;

		mul *= -1;
	}	
	cout << ans << endl;


}
int main(){
    // Use "set_name".max_load_factor(0.25);"set_name".reserve(512); with unordered set
    // Or use gp_hash_table<X,null_type>
    init();
    preprocess();
    ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
    cout<<fixed<<setprecision(25);
    cerr<<fixed<<setprecision(10);
    auto start = std::chrono::high_resolution_clock::now();
    int t=1;
    //cin >> t;
    while(t--) {
        solve();
    }
    auto stop = std::chrono::high_resolution_clock::now(); 
    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(stop - start); 
    
    // cerr << "Time taken : " << ((long double)duration.count())/((long double) 1e9) <<"s "<< endl;     
}

Measured memory_bytes: 31316