define_modint!(MInt; Modulus: i32 = 998244353);

use num::{One, Zero};

fn main() {
	let mut s = String::new();
	use std::io::Read;
	std::io::stdin().read_to_string(&mut s).unwrap();
	let mut s = s.split_whitespace();
	let n: usize = s.next().unwrap().parse().unwrap();
	let mut mp = std::collections::HashMap::new();
	for _ in 0..2 * n {
		let h = s.next().unwrap().parse::<usize>().unwrap() - 1;
		*mp.entry(h).or_insert(0) += 1;
	}
	let mut ps = std::collections::VecDeque::new();
	let mut p = 0;
	for (_, k) in mp {
		if k < 2 {
			continue;
		}
		p += k / 2;
		let mut x = MInt::one();
		let mut b = vec![MInt::one()];
		for i in (2..=k).step_by(2) {
			x = x * MInt::from((k - i + 2) * (k - i + 1) / 2) / MInt::from(i / 2);
			b.push(x);
		}
		ps.push_back(b);
	}

	while ps.len() > 1 {
		let a = ps.pop_front().unwrap();
		let b = ps.pop_front().unwrap();
		let c = karatsuba::<MInt>(&a, &b);
		ps.push_back(c);
	}
	let poly = ps.pop_front().unwrap_or(vec![MInt::one()]);

	let table = {
		let mut r = vec![MInt::one()];
		for i in 1..=n {
			let x = r[i - 1] * MInt::from(2 * i - 1);
			r.push(x);
		}
		r
	};

	let mut ans = MInt::zero();
	for i in 0..=p {
		if i % 2 == 0 {
			ans = ans + poly[i] * table[n - i];
		} else {
			ans = ans - poly[i] * table[n - i];
		}
	}
	println!("{}", ans);
}

pub mod traits {
	pub mod consts {
		pub trait Constant: super::marker::Unsafe {
			type Type;
			fn value() -> Self::Type;
		}

		unsafe impl<T: LiteralConstant> super::marker::Unsafe for T {}

		pub trait LiteralConstant {
			type Type;
			const VALUE: Self::Type;
		}

		impl<T: LiteralConstant> Constant for T {
			type Type = <Self as LiteralConstant>::Type;
			fn value() -> Self::Type {
				Self::VALUE
			}
		}

		macro_rules! impl_const {
			($trait:ident ,$struct:ident, $type:ty, $value:expr) => {
				pub struct $struct;
				impl $trait for $struct {}
				impl LiteralConstant for $struct {
					type Type = $type;
					const VALUE: $type = $value;
				}
			};
		}

		pub trait Bool: LiteralConstant<Type = bool> {}
		impl_const![Bool, True, bool, true];
		impl_const![Bool, False, bool, false];
	}

	pub mod marker {
		pub unsafe trait Unsafe {}
	}

	pub mod integer {
		pub trait SizeOrder<T> {
			type LargerType;
			type SmallerType;
		}

		pub trait IntoLarger {
			type LargerType;
		}

		macro_rules! impl_ {
			($i0:ty:$u0:ty, $($is:ty:$us:ty),+) => {
				impl_![imp $i0:$u0];
				impl_![imp $i0];
				impl_![imp $u0];
				$(impl_![imp $i0:$u0, $is:$us];)+
				impl_![$($is:$us),+];
			};

			($i:ty:$u:ty) => {
				impl_![imp $i:$u];
				impl_![imp $i];
				impl_![imp $u];
			};

			(imp $t:ty) => {
				impl_![imp $t | $t, $t:$t];
			};

			(imp $i:ty:$u:ty) => {
				impl_![imp $u | $i, $u:$i];
				impl_![imp $i | $u, $u:$i];
			};

			(imp $i0:ty:$u0:ty, $i1:ty:$u1:ty) => {
				impl_![imp $i1 | $i0, $i1:$i0];
				impl_![imp $u1 | $u0, $u1:$u0];
				impl_![imp $i1 | $u0, $i1:$u0];
				impl_![imp $u1 | $i0, $u1:$i0];
				impl_![imp $i0 | $i1, $i1:$i0];
				impl_![imp $u0 | $u1, $u1:$u0];
				impl_![imp $u0 | $i1, $i1:$u0];
				impl_![imp $i0 | $u1, $u1:$i0];
			};

			(imp $target:ty | $self:ty, $large:ty:$small:ty) => {
				impl SizeOrder<$target> for $self {
					type LargerType = $large;
					type SmallerType = $small;
				}
			};

			(lt ptr; $($is:ty:$us:ty),+) => {
				$(impl_![imp $is:$us, isize:usize];)+
			};
			(eq ptr; $($is:ty:$us:ty),+) => {
				impl_![isize:usize];
				$(
					impl_![imp $is | isize, $is:isize];
					impl_![imp $us | isize, $us:isize];
					impl_![imp $is | usize, usize:$is];
					impl_![imp $us | usize, $us:usize];
					impl_![imp isize | $is, $is:isize];
					impl_![imp isize | $us, $us:isize];
					impl_![imp usize | $is, usize:$is];
					impl_![imp usize | $us, $us:usize];
				)+
			};
			(gt ptr; $($is:ty:$us:ty),+) => {
				$(impl_![imp isize:usize, $is:$us];)+
			};
		}

		impl_![i8: u8, i16: u16, i32: u32, i64: u64, i128: u128];
		#[cfg(target_pointer_width = "32")]
		mod size_order_for_ptr {
			use super::SizeOrder;
			impl_![lt ptr; i8: u8, i16: u16];
			impl_![eq ptr; i32: u32];
			impl_![gt ptr; i64: u64, i128: u128];
		}

		#[cfg(target_pointer_width = "64")]
		mod size_order_for_ptr {
			use super::SizeOrder;
			impl_![lt ptr; i8: u8, i16: u16, i32: u32];
			impl_![eq ptr; i64: u64];
			impl_![gt ptr; i128: u128];
		}

		macro_rules! impl_ {
			($t0:ty, $t1:ty, $($ts:ty),+) => {
				impl_![imp $t0, $t1];
				impl_![$t1, $($ts),+];
			};

			($t0:ty, $t1:ty) => {
				impl_![imp $t0, $t1];
			};

			(imp $t0:ty, $t1:ty) => {
				impl IntoLarger for $t0 {
					type LargerType = $t1;
				}
			};
		}

		impl_![i8, i16, i32, i64, i128];
		impl_![u8, u16, u32, u64, u128];
	}
}

pub mod math {
	pub mod number {
		pub mod modint {
			use crate::traits::consts::Constant;
			use crate::traits::integer::{IntoLarger, SizeOrder};
			use num::traits::{AsPrimitive, One, Zero};
			use std::ops::{Add, Div, Mul, Sub};

			pub struct ModInt<M>
			where
				M: Constant,
			{
				value: M::Type,
			}

			impl<T, U, M> From<T> for ModInt<M>
			where
				M: Constant,
				M::Type: num::Integer + num::Signed + SizeOrder<T, LargerType = U> + AsPrimitive<U>,
				T: num::Integer + SizeOrder<M::Type, LargerType = U> + AsPrimitive<U>,
				U: num::Integer + AsPrimitive<M::Type>,
			{
				fn from(src: T) -> Self {
					debug_assert!(M::value() >= M::Type::zero());
					let src: U = src.as_();
					let modulus: U = M::value().as_();
					let value = num::integer::mod_floor(src, modulus).as_();
					Self { value }
				}
			}

			impl<M> std::fmt::Display for ModInt<M>
			where
				M: Constant,
				M::Type: std::fmt::Display,
			{
				fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
					write!(f, "{}", self.value)
				}
			}

			impl<M> std::fmt::Debug for ModInt<M>
			where
				M: Constant,
				M::Type: std::fmt::Debug,
			{
				fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
					f.debug_tuple("ModInt").field(&self.value).finish()
				}
			}

			impl<M> Clone for ModInt<M>
			where
				M: Constant,
				M::Type: Clone,
			{
				fn clone(&self) -> Self {
					Self {
						value: self.value.clone(),
					}
				}
			}

			impl<M> Copy for ModInt<M>
			where
				M: Constant,
				M::Type: Copy,
			{
			}

			impl<M> Default for ModInt<M>
			where
				M: Constant,
				M::Type: Default,
			{
				fn default() -> Self {
					Self {
						value: M::Type::default(),
					}
				}
			}

			macro_rules! impl_std_ops {
				($trait:ident:$fn:ident for $type:ty) => {
					impl_std_ops!($type; $trait:$fn);
					impl_std_ops!($type; $trait:$fn<'a, &>);
					impl_std_ops!($type; <'a, &> $trait:$fn);
					impl_std_ops!($type; <'a, &> $trait:$fn<'b, &>);
				};

				($type:ty; $(<$lt_self:lifetime, $ref_self:tt>)? Add:$fn:ident $(<$lt_rhs:lifetime, $ref_rhs:tt>)?) => {
					impl<$($lt_self,)? $($lt_rhs, )?M> Add<$($ref_rhs$lt_rhs)?$type> for $($ref_self$lt_self)? $type
					where
						M: Constant,
						M::Type: Copy + num::Integer,
					{
						type Output = $type;
						fn $fn(self, rhs: $($ref_rhs$lt_rhs)?$type) -> Self::Output {
							let value = self.value.$fn(rhs.value);
							ModInt { value: if value >= M::value() { value - M::value() } else { value } }
						}
					}
				};

				($type:ty; $(<$lt_self:lifetime, $ref_self:tt>)? Sub:$fn:ident $(<$lt_rhs:lifetime, $ref_rhs:tt>)?) => {
					impl<$($lt_self,)? $($lt_rhs, )?M> Sub<$($ref_rhs$lt_rhs)?$type> for $($ref_self$lt_self)? $type
					where
						M: Constant,
						M::Type: Copy + num::Integer + num::Signed,
					{
						type Output = $type;
						fn $fn(self, rhs: $($ref_rhs$lt_rhs)?$type) -> Self::Output {
							let value = self.value.$fn(rhs.value);
							ModInt { value: if value < M::Type::zero() { value + M::value() } else { value } }
						}
					}
				};

				($type:ty; $(<$lt_self:lifetime, $ref_self:tt>)? Mul:$fn:ident $(<$lt_rhs:lifetime, $ref_rhs:tt>)?) => {
					impl<$($lt_self,)? $($lt_rhs, )?M> Mul<$($ref_rhs$lt_rhs)?$type> for $($ref_self$lt_self)? $type
					where
						M: Constant,
						M::Type: num::Integer + IntoLarger + AsPrimitive<<M::Type as IntoLarger>::LargerType>,
						<M::Type as IntoLarger>::LargerType: num::Integer + AsPrimitive<M::Type>,
					{
						type Output = $type;
						fn $fn(self, rhs: $($ref_rhs$lt_rhs)?$type) -> Self::Output {
							let modulus: <M::Type as IntoLarger>::LargerType = M::value().as_();
							let lhs: <M::Type as IntoLarger>::LargerType = self.value.as_();
							let rhs: <M::Type as IntoLarger>::LargerType = rhs.value.as_();
							let value: M::Type = num::integer::mod_floor(lhs.$fn(rhs), modulus).as_();
							ModInt { value }
						}
					}
				};

				($type:ty; $(<$lt_self:lifetime, $ref_self:tt>)? Div:$fn:ident $(<$lt_rhs:lifetime, $ref_rhs:tt>)?) => {
					impl<$($lt_self,)? $($lt_rhs, )?M> Div<$($ref_rhs$lt_rhs)?$type> for $($ref_self$lt_self)? $type
					where
						M: Constant,
						M::Type: Copy + num::Integer + num::Signed,
						$($ref_self$lt_self)?$type : Mul<$type, Output = $type>
					{
						type Output = $type;
						fn $fn(self, rhs: $($ref_rhs$lt_rhs)?$type) -> Self::Output {
							self * rhs.inverse()
						}
					}
				};
			}

			impl_std_ops!(Add:add for ModInt<M>);
			impl_std_ops!(Sub:sub for ModInt<M>);
			impl_std_ops!(Mul:mul for ModInt<M>);
			impl_std_ops!(Div:div for ModInt<M>);

			impl<M> std::ops::Neg for ModInt<M>
			where
				M: Constant,
				M::Type: num::Integer + num::Signed,
			{
				type Output = Self;
				fn neg(self) -> Self::Output {
					Self {
						value: if self.value == M::Type::zero() {
							M::Type::zero()
						} else {
							M::value() - self.value
						},
					}
				}
			}

			impl<M> Zero for ModInt<M>
			where
				M: Constant,
				M::Type: num::Zero + num::Integer,
				Self: Add<Output = Self>,
			{
				fn zero() -> Self {
					Self {
						value: M::Type::zero(),
					}
				}

				fn is_zero(&self) -> bool {
					self.value == M::Type::zero()
				}
			}

			impl<M, U> num::traits::Pow<U> for ModInt<M>
			where
				M: Constant,
				Self: Copy + One,
				U: num::Integer + num::Unsigned,
			{
				type Output = Self;
				fn pow(self, rhs: U) -> Self {
					let mut r = Self::one();
					let mut b = rhs;
					let mut a = self;
					while b > U::zero() {
						if b.is_odd() {
							r = r * a;
						}
						a = a * a;
						b = b / (U::one() + U::one());
					}
					r
				}
			}

			impl<M> One for ModInt<M>
			where
				M: Constant,
				M::Type: num::Integer,
				Self: Mul<Output = Self>,
			{
				fn one() -> Self {
					Self {
						value: M::Type::one(),
					}
				}
			}

			impl<M> ModInt<M>
			where
				M: Constant,
			{
				pub fn value(&self) -> &M::Type {
					&self.value
				}
			}

			impl<M> ModInt<M>
			where
				M: Constant,
				M::Type: Copy + num::Integer + num::Signed,
			{
				pub fn inverse(self) -> Self {
					let mut r = (M::value(), self.value);
					let mut s = (M::Type::zero(), M::Type::one());
					while r.1 != M::Type::zero() {
						let q = r.0 / r.1;
						let nr = r.0 - q * r.1;
						r = (r.1, nr);
						let ns = s.0 - q * s.1;
						s = (s.1, ns);
					}
					if r.0 > M::Type::one() {
						panic!();
					}
					Self {
						value: if s.0 < M::Type::zero() {
							s.0 + M::value()
						} else {
							s.0
						},
					}
				}

				pub fn pow<U>(self, mut exp: U) -> Self
				where
					Self: One,
					U: num::Integer + num::Unsigned,
				{
					let mut r = Self::one();
					let mut a = self;
					while exp > U::zero() {
						if exp.is_odd() {
							r = r * a;
						}
						a = a * a;
						exp = exp / (U::one() + U::one());
					}
					r
				}
			}

			#[macro_export]
			macro_rules! define_modint {
				($mint:ident; $modulus:ident: $type:ty = $expr:expr) => {
					type $mint = $crate::math::number::modint::ModInt<$modulus>;
					struct $modulus;
					impl $crate::traits::consts::LiteralConstant for $modulus {
						type Type = $type;
						const VALUE: $type = $expr;
					}
				};
			}

			#[macro_export]
			macro_rules! define_runtime_modint {
				($mint:ident; $modulus:ident with $mod:ident: $type:ty) => {
					type $mint = $crate::math::number::modint::ModInt<$modulus>;
					thread_local! { static $mod: std::cell::RefCell<$type> = std::cell::RefCell::new(0); }
					struct $modulus;
					impl $modulus {
						pub fn with<T, F: FnOnce() -> T>(modulus: $type, f: F) -> T {
							$mod.with(|m| {
								*m.borrow_mut() = modulus;
								let _m = m.borrow();
								f()
							})
						}
					}
					unsafe impl traits::marker::Unsafe for $modulus {}
					impl traits::consts::Constant for $modulus {
						type Type = $type;
						fn value() -> Self::Type {
							$mod.with(|m| *m.borrow())
						}
					}
				};
			}
		}
	}
}

use crate::traits::consts::{Bool, False, True};
use std::ops::{Add, Mul, Sub};

pub fn karatsuba<T>(a: &[T], b: &[T]) -> Vec<T>
where
	T: Clone + num::Zero + Sub<Output = T> + Mul<Output = T>,
	for<'a> &'a T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Add<T, Output = T>,
{
	fn internal<T, Overwrite: Bool>(a: &[T], b: &[T], r: &mut [T], scratch: &mut [T]) -> usize
	where
		T: Clone + num::Zero + Sub<Output = T> + Mul<Output = T>,
		for<'a> &'a T: Add<Output = T> + Sub<Output = T> + Mul<Output = T> + Add<T, Output = T>,
	{
		let (a, b) = if a.len() <= b.len() { (a, b) } else { (b, a) };
		if a.len() == 0 {
			return 0;
		}
		if b.len() < 16 {
			if Overwrite::VALUE {
				for r in r.iter_mut() {
					*r = T::zero();
				}
			}
			for i in 0..a.len() {
				for j in 0..b.len() {
					unsafe {
						*r.get_unchecked_mut(i + j) =
							r.get_unchecked(i + j) + a.get_unchecked(i) * b.get_unchecked(j);
					}
				}
			}
			a.len() + b.len() - 1
		} else {
			let k = (b.len() + 1) / 2;

			let (sc1, sc2) = scratch.split_at_mut(2 * k);
			let ac = internal::<T, True>(&a[0..std::cmp::min(k, a.len())], &b[0..k], sc1, sc2);
			if Overwrite::VALUE {
				unsafe {
					let l = std::cmp::min(ac, k);
					for i in 0..l {
						*r.get_unchecked_mut(i) = sc1.get_unchecked(i).clone();
						*r.get_unchecked_mut(i + k) = T::zero() - sc1.get_unchecked(i).clone();
					}
					if l == k {
						for i in k..ac {
							*r.get_unchecked_mut(i) = r.get_unchecked(i) + sc1.get_unchecked(i);
							*r.get_unchecked_mut(i + k) = T::zero() - sc1.get_unchecked(i).clone();
						}
					} else {
						for r in r[ac..k].iter_mut() {
							*r = T::zero();
						}
					}
					for r in r[k + ac..3 * k].iter_mut() {
						*r = T::zero();
					}
				}
				let bc = internal::<T, True>(&a[std::cmp::min(k, a.len())..], &b[k..], sc1, sc2);
				unsafe {
					let l = std::cmp::min(bc, k);
					for i in 0..l {
						*r.get_unchecked_mut(i + 2 * k) =
							r.get_unchecked(i + 2 * k) + sc1.get_unchecked(i);
						*r.get_unchecked_mut(i + k) = r.get_unchecked(i + k) - sc1.get_unchecked(i);
					}
					if l == k {
						for i in k..bc {
							*r.get_unchecked_mut(i + 2 * k) = sc1.get_unchecked(i).clone();
							*r.get_unchecked_mut(i + k) =
								r.get_unchecked(i + k) - sc1.get_unchecked(i);
						}
					}
				}
			} else {
				for i in 0..ac {
					unsafe {
						*r.get_unchecked_mut(i) = r.get_unchecked(i) + sc1.get_unchecked(i);
						*r.get_unchecked_mut(i + k) = r.get_unchecked(i + k) - sc1.get_unchecked(i);
					}
				}
				let bc = internal::<T, True>(&a[std::cmp::min(k, a.len())..], &b[k..], sc1, sc2);
				for i in 0..bc {
					unsafe {
						*r.get_unchecked_mut(i + 2 * k) =
							r.get_unchecked(i + 2 * k) + sc1.get_unchecked(i);
						*r.get_unchecked_mut(i + k) = r.get_unchecked(i + k) - sc1.get_unchecked(i);
					}
				}
			}

			{
				let l = b.len() - k;
				for (sc, (l, h)) in scratch[..l]
					.iter_mut()
					.zip(b[..l].iter().zip(b[k..k + l].iter()))
				{
					*sc = l + h;
				}
				for (sc, e) in scratch[l..k].iter_mut().zip(b[l..k].iter()) {
					*sc = e.clone();
				}
			}

			if a.len() < k {
				let (src, sc) = scratch.split_at_mut(k);
				internal::<T, False>(a, src, &mut r[k..3 * k], sc);
			} else {
				{
					let l = a.len() - k;
					for (sc, (l, h)) in scratch[k..k + l]
						.iter_mut()
						.zip(a[..l].iter().zip(a[k..k + l].iter()))
					{
						*sc = l + h;
					}
					for (sc, e) in scratch[k + l..2 * k].iter_mut().zip(a[l..k].iter()) {
						*sc = e.clone();
					}
				}

				let (src, sc) = scratch.split_at_mut(2 * k);
				internal::<T, False>(&src[k..], &src[..k], &mut r[k..3 * k], sc);
			}

			a.len() + b.len() - 1
		}
	}
	if a.len() == 0 || b.len() == 0 {
		return Vec::new();
	}
	let n = std::cmp::max(a.len(), b.len()) + std::mem::size_of::<usize>() * 8;
	let mut v = vec![T::zero(); n * 4];
	let (r, scratch) = v.split_at_mut(2 * n);
	internal::<T, False>(a, b, r, scratch);
	v.truncate(a.len() + b.len() - 1);
	v
}


Measured memory_bytes: 15992