@file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_FEATURE_WARNING", "OVERRIDE_BY_INLINE")
//@file:OptIn(ExperimentalStdlibApi::class)

import java.io.PrintWriter
import java.util.PriorityQueue
import kotlin.math.*
import kotlin.random.Random
import kotlin.collections.sort as _sort
import kotlin.collections.sortDescending as _sortDescending
import kotlin.io.println as iprintln


/** @author Spheniscine */
fun main() { _writer.solve(); _writer.flush() }
fun PrintWriter.solve() {
//    val startTime = System.nanoTime()

    val numCases = 1//readInt()
    case@ for(case in 1..numCases) {
//        print("Case #$case: ")

        val n = readInt()
        val H = readIntArray(n+n)
        H.sort()

        fft = ModFFT(n)
        val polys = Poly.convolutionQueue()

        ArrayModCombinatorics(n+n).run {
            val halfPow = invf[2].powArray(n)
            val pairs = ModIntArray(n+1) { m ->
                factorial[m+m] * invf[m] * halfPow[m]
            }

            H.forEachRun { _, a ->
                if(a > 1) {
                    val P = ModIntArray(a / 2 + 1) { j ->
                        C(a, j + j) * pairs[j]
                    }

                    polys.add(Poly(P))
                }
            }

            val P = polys.convolveAll()
            var ans = ModInt(0)

            for(i in 0 .. min(n, P.lastIndex)) {
                val r = P[i] * pairs[n-i]
                if(i and 1 == 0) ans += r else ans -= r
            }

            println(ans.int)
        }

    }

//    iprintln("Time: ${(System.nanoTime() - startTime) / 1000000} ms")
}

fun PriorityQueue<Poly>.convolveAll(): ModIntArray {
    if(isEmpty()) return ModIntArray(intArrayOf(1))
    while(size > 1) {
        val a = remove()
        val b = remove()
        a.mul(b)
        add(a)
    }

    element().dft = false
    return element().arr
}

inline fun IntArray.forEachRun(yield: (item: Int, num: Int) -> Unit) {
    if(isEmpty()) return
    var last = this[0]
    var num = 1

    for(i in 1 until size) {
        if(last == this[i]) num++
        else {
            yield(last, num)
            last = this[i]
            num = 1
        }
    }
    yield(last, num)
}

class ArrayModCombinatorics(val maxn: Int) {
    val factorial = ModIntArray(maxn + 1).also {
        it[0] = ModInt(1)
        for(i in 1 .. maxn) {
            it[i] = it[i-1] * i
        }
    }

    val invf = ModIntArray(maxn + 1).also {
        it[maxn] = factorial[maxn].inv_unmemoized()
        for(i in maxn downTo 1) {
            it[i-1] = it[i] * i
        }
    }

    fun P(n: Int, k: Int) = if(k > n) ModInt(0) else factorial[n] * invf[n-k]
    fun C(n: Int, k: Int) = if(k > n) ModInt(0) else factorial[n] * invf[k] * invf[n-k]

    // multi-choose, be sure to adjust maxn accordingly
    fun M(n: Int, k: Int) = if(k == 0) ModInt(1) else C(n + k - 1, k)
}

lateinit var fft: ModFFT
class Poly(var arr: ModIntArray, var deg: Int = arr.lastIndex) {
    companion object {
        val degComparator = compareBy<Poly> { it.deg }
        fun convolutionQueue() = PriorityQueue<Poly>(degComparator)
    }

    var dft = false
        set(value) {
            if(field == value) return
            field = value
            fft.inPlace(arr, !value)
        }

    fun mul(b: Poly) {
        val arrSize = fft.getArrSize(deg + b.deg)
        if(arrSize != arr.size) {
            dft = false
            arr = arr.copyOf(arrSize)
        }
        if(arrSize != b.arr.size) {
            b.dft = false
            b.arr = b.arr.copyOf(arrSize)
        }

        dft = true
        b.dft = true
        for(i in arr.indices) arr[i] *= b.arr[i]
        deg += b.deg
    }
}

inline val PRIMITIVE_ROOT get() = ModInt(3) // for 998244353
class ModFFT(maxDeg: Int) {
    val n = getArrSize(maxDeg)
    private val ninv = ModInt(n).inv_unmemoized()

    fun getArrSize(deg: Int) = Integer.highestOneBit(deg shl 1 or 1)
    // prepares an array of suitable size for inPlace
    fun prepare(arr: ModIntArray, deg: Int) = arr.copyOf(getArrSize(deg))

    // w[1] represents n-th root of unity e^(i * tau/n), w[k] = w[1]^k
    val w = PRIMITIVE_ROOT.pow(TOTIENT / n).let { w1 ->
        val w = ModIntArray(n)
        w[0] = ModInt(1)
        if(n > 1) {
            val h = n shr 1
            for (i in 1 until h) w[i] = w[i-1] * w1
            for (i in h until n) w[i] = -w[i - h]
        }
        w
    }

    // rev[i] is reversal of bits in i
    private val rev = IntArray(n).also { rev ->
        var bit = 1
        var rbit = n shr 1
        while(bit < n) {
            for(i in 0 until bit) {
                rev[i or bit] = rbit or rev[i]
            }
            bit = bit shl 1
            rbit = rbit shr 1
        }
    }

    operator fun invoke(a: ModIntArray, invert: Boolean): ModIntArray {
        val res = a.copyOf(n)
        inPlace(res, invert)
        return res
    }

    // in-place FFT. Precondition: a.size <= n, and a.size is a power of 2
    fun inPlace(a: ModIntArray, invert: Boolean) {
        val sz = a.size
        if(sz <= 1) return
        require(sz <= n && sz and -sz == sz) { "Array size $sz must be less than $n and a power of 2" }
        val st = n / sz
        for(i in 0 until sz) {
            val j = rev[i * st]
            if(i < j) a[i] = a[j].also { a[j] = a[i] }
        }

        var len = 2
        var ang = n shr 1 // = n / len, representing the angle tau / len
        if(invert) ang = -ang
        while(len <= sz) {
            var i = 0
            val h = len shr 1
            while(i < sz) {
                var k = 0
                for(j in i until i+h) {
                    val u = a[j]
                    val v = a[j+h] * w[k]
                    a[j] = u + v
                    a[j+h] = u - v
                    k = k + ang and n-1
                }
                i += len
            }

            len = len shl 1
            ang = ang shr 1
        }

        if(invert) {
            val szinv = ninv * st
            for(i in 0 until sz) a[i] *= szinv
        }
    }

    fun mul(a: ModIntArray, b: ModIntArray): ModIntArray {
        val deg = a.lastIndex + b.lastIndex
        require(deg < n) { "FFT capacity $n too small for polynomial of degree $deg" }
        val a1 = prepare(a, deg)
        val b1 = prepare(b, deg)
        inPlace(a1, false)
        inPlace(b1, false)
        for(i in a1.indices) a1[i] *= b1[i]
        inPlace(a1, true)
        return a1//.copyOf(deg+1)
    }

    fun sq(a: ModIntArray): ModIntArray {
        val deg = a.lastIndex * 2
        require(deg < n) { "FFT capacity $n too small for polynomial of degree $deg" }
        val a1 = prepare(a, deg)
        inPlace(a1, false)
        for(i in a1.indices) a1[i] *= a1[i]
        inPlace(a1, true)
        return a1//.copyOf(deg+1)
    }
}


const val BILLION7 = 1e9.toInt() + 7
const val MOD = 998244353
const val TOTIENT = MOD - 1 // assumes MOD is prime

infix fun Int.modulo(mod: Int): Int = (this % mod).let { (it shr Int.SIZE_BITS - 1 and mod) + it }
infix fun Long.modulo(mod: Long) = (this % mod).let { (it shr Long.SIZE_BITS - 1 and mod) + it }
infix fun Long.modulo(mod: Int) = modulo(mod.toLong()).toInt()

fun Int.mulMod(other: Int, mod: Int) = toLong() * other modulo mod

fun Int.powMod(exponent: Long, mod: Int): Int {
    if(exponent < 0) error("Inverse not implemented")
    var res = 1L
    var e = exponent
    var b = modulo(mod).toLong()

    while(e > 0) {
        if(e and 1 == 1L) {
            res = res * b % mod
        }
        e = e shr 1
        b = b * b % mod
    }
    return res.toInt()
}
fun Int.powMod(exponent: Int, mod: Int) = powMod(exponent.toLong(), mod)
fun Int.modPowArray(n: Int, mod: Int): IntArray {
    val res = IntArray(n+1)
    res[0] = 1
    for(i in 1..n) res[i] = mulMod(res[i-1], mod)
    return res
}

inline fun Int.toModInt() = ModInt(this modulo MOD)
inline fun Long.toModInt() = ModInt(this modulo MOD)

/** note: Only use constructor for int within modulo range, otherwise use toModInt **/
inline class ModInt(val int: Int) {
    companion object {
        /** can't seem to make these private or inlined without causing compiler issues */
        @JvmField val _invMemo = HashMap<ModInt, ModInt>()
        fun _invMemoized(m: ModInt) = _invMemo.getOrPut(m) { m.inv_unmemoized() }
    }

    // normalizes an integer that's within range [-MOD, MOD) without branching
    private inline fun normalize(int: Int) = ModInt((int shr Int.SIZE_BITS - 1 and MOD) + int)

    operator fun plus(other: ModInt) = normalize(int + other.int - MOD) // overflow-safe even if MOD >= 2^30
    inline operator fun plus(other: Int) = plus(other.toModInt())
    operator fun inc() = normalize(int + (1 - MOD))

    operator fun minus(other: ModInt) = normalize(int - other.int)
    inline operator fun minus(other: Int) = minus(other.toModInt())
    operator fun dec() = normalize(int - 1)
    operator fun unaryMinus() = normalize(-int)

    operator fun times(other: ModInt) = ModInt((int.toLong() * other.int % MOD).toInt())
    inline operator fun times(other: Int) = ModInt(int.mulMod(other, MOD))

    fun pow(exponent: Int): ModInt {
        val e = if(exponent < 0) {
            require(int != 0) { "Can't invert/divide by 0" }
            exponent modulo TOTIENT
        } else exponent
        return ModInt(int.powMod(e, MOD))
    }

    fun pow(exponent: Long) = if(int == 0) when {
        exponent > 0 -> this
        exponent == 0L -> ModInt(1)
        else -> error("Can't invert/divide by 0")
    } else pow(exponent modulo TOTIENT)

    inline fun inverse() = inv_memoized() /** NOTE: Change if necessary */

    fun inv_unmemoized(): ModInt {
        require(int != 0) { "Can't invert/divide by 0" }
        return pow(TOTIENT - 1)
    }
    inline fun inv_memoized() = _invMemoized(this)

    operator fun div(other: ModInt) = times(other.inverse())
    inline operator fun div(other: Int) = div(other.toModInt())

    override inline fun toString() = int.toString()
}

inline operator fun Int.plus(modInt: ModInt) = modInt + this
inline operator fun Int.minus(modInt: ModInt) = toModInt() - modInt
inline operator fun Int.times(modInt: ModInt) = modInt * this
inline operator fun Int.div(modInt: ModInt) = modInt.inverse() * this

inline class ModIntArray(val intArray: IntArray): Collection<ModInt> {
    inline operator fun get(i: Int) = ModInt(intArray[i])
    inline operator fun set(i: Int, v: ModInt) { intArray[i] = v.int }

    override inline val size: Int get() = intArray.size
    inline val lastIndex get() = intArray.lastIndex
    inline val indices get() = intArray.indices

    override inline fun contains(element: ModInt): Boolean = element.int in intArray

    override fun containsAll(elements: Collection<ModInt>): Boolean = elements.all(::contains)

    override inline fun isEmpty(): Boolean = intArray.isEmpty()

    override fun iterator(): Iterator<ModInt> = object: Iterator<ModInt> {
        var index = 0
        override fun hasNext(): Boolean = index < size
        override fun next(): ModInt = get(index++)
    }

    fun copyOf(newSize: Int) = ModIntArray(intArray.copyOf(newSize))
    fun copyOf() = copyOf(size)
}
fun ModIntArray.copyInto(destination: ModIntArray, destinationOffset: Int = 0, startIndex: Int = 0, endIndex: Int = size) =
    ModIntArray(intArray.copyInto(destination.intArray, destinationOffset, startIndex, endIndex))
inline fun ModIntArray(size: Int) = ModIntArray(IntArray(size))
inline fun ModIntArray(size: Int, init: (Int) -> ModInt) = ModIntArray(IntArray(size) { init(it).int })

fun ModInt.powArray(n: Int) = ModIntArray(int.modPowArray(n, MOD))

inline fun ModIntArray.first() = get(0)
inline fun ModIntArray.last() = get(lastIndex)
inline fun ModIntArray.joinToString(separator: CharSequence) = intArray.joinToString(separator)
inline fun <R> ModIntArray.fold(init: R, op: (acc: R, ModInt) -> R) = intArray.fold(init) { acc, i -> op(acc, ModInt(i)) }
inline fun <R> ModIntArray.foldRight(init: R, op: (ModInt, acc: R) -> R) = intArray.foldRight(init) { i, acc -> op(ModInt(i), acc) }
fun ModIntArray.sum() = fold(ModInt(0), ModInt::plus)
fun ModIntArray.product() = fold(ModInt(1), ModInt::times)

inline fun <T> Iterable<T>.sumByModInt(func: (T) -> ModInt) = fold(ModInt(0)) { acc, t -> acc + func(t) }
inline fun <T> Iterable<T>.productByModInt(func: (T) -> ModInt) = fold(ModInt(1)) { acc, t -> acc * func(t) }
inline fun <T> Sequence<T>.sumByModInt(func: (T) -> ModInt) = fold(ModInt(0)) { acc, t -> acc + func(t) }
inline fun <T> Sequence<T>.productByModInt(func: (T) -> ModInt) = fold(ModInt(1)) { acc, t -> acc * func(t) }
inline fun <T> Array<T>.sumByModInt(func: (T) -> ModInt) = fold(ModInt(0)) { acc, t -> acc + func(t) }
inline fun <T> Array<T>.productByModInt(func: (T) -> ModInt) = fold(ModInt(1)) { acc, t -> acc * func(t) }
fun Iterable<ModInt>.sum() = sumByModInt { it }
fun Sequence<ModInt>.sum() = sumByModInt { it }
fun Iterable<ModInt>.product() = productByModInt { it }
fun Sequence<ModInt>.product() = productByModInt { it }
fun Collection<ModInt>.toModIntArray() = ModIntArray(size).also { var i = 0; for(e in this) { it[i++] = e } }



/** IO */
//@JvmField val INPUT = File("input.txt").inputStream()
//@JvmField val OUTPUT = File("output.txt").outputStream()
@JvmField val INPUT = System.`in`
@JvmField val OUTPUT = System.out

const val _BUFFER_SIZE = 1 shl 16
@JvmField val _buffer = ByteArray(_BUFFER_SIZE)
@JvmField var _bufferPt = 0
@JvmField var _bytesRead = 0

tailrec fun readChar(): Char {
    if(_bufferPt == _bytesRead) {
        _bufferPt = 0
        _bytesRead = INPUT.read(_buffer, 0, _BUFFER_SIZE)
    }
    return if(_bytesRead < 0) Char.MIN_VALUE
    else {
        val c = _buffer[_bufferPt++].toChar()
        if (c == '\r') readChar()
        else c
    }
}

/** @param skipNext Whether to skip the next character (usually whitespace), defaults to true */
fun readCharArray(n: Int, skipNext: Boolean = true): CharArray {
    val res = CharArray(n) { readChar() }
    if(skipNext) readChar()
    return res
}

fun readLine(): String? {
    var c = readChar()
    return if(c == Char.MIN_VALUE) null
    else buildString {
        while(c != '\n' && c != Char.MIN_VALUE) {
            append(c)
            c = readChar()
        }
    }
}
fun readLn() = readLine()!!

fun read() = buildString {
    var c = readChar()
    while(c <= ' ') {
        if(c == Char.MIN_VALUE) return@buildString
        c = readChar()
    }
    do {
        append(c)
        c = readChar()
    } while(c > ' ')
}
fun readInt() = read().toInt()
fun readDouble() = read().toDouble()
fun readLong() = read().toLong()
fun readStrings(n: Int) = List(n) { read() }
fun readLines(n: Int) = List(n) { readLn() }
fun readInts(n: Int) = List(n) { read().toInt() }
fun readIntArray(n: Int) = IntArray(n) { read().toInt() }
fun readDoubles(n: Int) = List(n) { read().toDouble() }
fun readDoubleArray(n: Int) = DoubleArray(n) { read().toDouble() }
fun readLongs(n: Int) = List(n) { read().toLong() }
fun readLongArray(n: Int) = LongArray(n) { read().toLong() }

@JvmField val _writer = PrintWriter(OUTPUT, false)

/** shuffles and sort overrides to avoid quicksort attacks */
private inline fun <T> _shuffle(rnd: Random, get: (Int) -> T, set: (Int, T) -> Unit, size: Int) {
    // Fisher-Yates shuffle algorithm
    for (i in size - 1 downTo 1) {
        val j = rnd.nextInt(i + 1)
        val temp = get(i)
        set(i, get(j))
        set(j, temp)
    }
}

@JvmField var _random: Random? = null
val random get() = _random ?: Random(0x594E215C123 * System.nanoTime()).also { _random = it }

fun IntArray.shuffle(rnd: Random = random) = _shuffle(rnd, ::get, ::set, size)
fun IntArray.sort() { shuffle(); _sort() }
fun IntArray.sortDescending() { shuffle(); _sortDescending() }

fun LongArray.shuffle(rnd: Random = random) = _shuffle(rnd, ::get, ::set, size)
fun LongArray.sort() { shuffle(); _sort() }
fun LongArray.sortDescending() { shuffle(); _sortDescending() }

fun DoubleArray.shuffle(rnd: Random = random) = _shuffle(rnd, ::get, ::set, size)
fun DoubleArray.sort() { shuffle(); _sort() }
fun DoubleArray.sortDescending() { shuffle(); _sortDescending() }

fun CharArray.shuffle(rnd: Random = random) = _shuffle(rnd, ::get, ::set, size)
inline fun CharArray.sort() { _sort() }
inline fun CharArray.sortDescending() { _sortDescending() }

inline fun <T: Comparable<T>> Array<out T>.sort() = _sort()
inline fun <T: Comparable<T>> Array<out T>.sortDescending() = _sortDescending()
inline fun <T: Comparable<T>> MutableList<out T>.sort() = _sort()
inline fun <T: Comparable<T>> MutableList<out T>.sortDescending() = _sortDescending()

// import preserving junk function
@Suppress("NonAsciiCharacters") fun 雪花飄飄北風嘯嘯天地一片蒼茫() { iprintln(max(1, 2)) }

/** additional commons */
inline fun <T> Iterable<T>.sumByLong(func: (T) -> Long) = fold(0L) { acc, t -> acc + func(t) }
inline fun <T> Sequence<T>.sumByLong(func: (T) -> Long) = fold(0L) { acc, t -> acc + func(t) }
inline fun <T> Array<T>.sumByLong(func: (T) -> Long) = fold(0L) { acc, t -> acc + func(t) }
fun IntArray.sumLong() = fold(0L, Long::plus)


Measured memory_bytes: 63264