Problem:
Score : 600 points

Problem Statement

There are 2N people numbered 1 through 2N.
The height of Person i is h_i.

How many ways are there to make N pairs of people such that the following conditions are satisfied?
Compute the answer modulo 998,244,353.

Each person is contained in exactly one pair.

For each pair, the heights of the two people in the pair are different.

Two ways are considered different if for some p and q, Person p and Person q are paired in one way and not in the other.

Constraints

1 \leq N \leq 50,000

1 \leq h_i \leq 100,000

All values in input are integers.

Input

Input is given from Standard Input in the following format:

N
h_1
:
h_{2N}

Output

Print the answer.

Sample Input 1

2
1
1
2
3

Sample Output 1

2

There are two ways:

Form the pair (Person 1, Person 3) and the pair (Person 2, Person 4).

Form the pair (Person 1, Person 4) and the pair (Person 2, Person 3).

Sample Input 2

5
30
10
20
40
20
10
10
30
50
60

Sample Output 2

516

Solution in C# (.NET Core 3.1.201) (judge verdict: Time Limit Exceeded)

using System;
using System.Linq;
using CompLib.Util;
using System.Threading;
using CompLib.Algorithm;
using CompLib.Mathematics;

public class Program
{
    private int N;
    private int[] H;
    private const int MaxH = 100000;
    private const int Lg = 16;
    private const int Len = 1 << Lg;

    public void Solve()
    {
        var sc = new Scanner();
        N = sc.NextInt();
        H = new int[2 * N];
        for (int i = 0; i < 2 * N; i++)
        {
            H[i] = sc.NextInt();
        }

        int[] cnt = new int[MaxH + 1];
        foreach (int i in H)
        {
            cnt[i]++;
        }

        var p = new ModInt[N + 1];
        p[0] = 1;
        for (int i = 1; i <= N; i++)
        {
            p[i] = p[i - 1] * (2 * i - 1);
        }

        var a = new ModInt[Len];
        a[0] = 1;
        var map = new ModInt[MaxH + 1][];
        FastFourierTransform.Transform(a, Lg, Len, false);
        for (int i = 0; i <= MaxH; i++)
        {
            if (cnt[i] < 2) continue;
            if (map[cnt[i]] == null)
            {
                map[cnt[i]] = new ModInt[Len];
                for (int j = 0; j * 2 <= cnt[i]; j += 1)
                {
                    map[cnt[i]][j] = Combinatorics.C(cnt[i], j * 2) * p[j];
                }

                FastFourierTransform.Transform(map[cnt[i]], Lg, Len, false);
            }

            for (int j = 0; j < Len; j++)
            {
                a[j] *= map[cnt[i]][j];
            }
        }

        FastFourierTransform.Transform(a, Lg, Len, true);

        var invLen = ModInt.Inverse(Len);
        for (int i = 0; i < Len; i++)
        {
            a[i] *= invLen;
        }




        ModInt ans = 0;

        for (int i = 0; i <= N; i++)
        {
            // iペア
            // N-iペア かぶる
            ans += p[i] * a[(N - i)] * ((N - i) % 2 == 0 ? 1 : -1);
        }

        Console.WriteLine(ans);
    }

    public static void Main(string[] args) => new Program().Solve();
    // public static void Main(string[] args) => new Thread(new Program().Solve, 1 << 27).Start();
}

namespace CompLib.Algorithm
{
    using System;
    using System.Numerics;

    public static class FastFourierTransform
    {
        private const int E = 23;
        public static readonly ModInt O = new ModInt(31);

        // private const int E = 20;
        // public static readonly ModInt O = new ModInt(646);
        // public static readonly ModInt InvO = new ModInt(208611436);

        // Z[i] 1の原始2^i乗根
        public static readonly ModInt[] Z;
        public static readonly ModInt[] InvZ;

        static FastFourierTransform()
        {
            Z = new ModInt[E + 1];
            InvZ = new ModInt[E + 1];
            Z[E] = O;
            InvZ[E] = ModInt.Inverse(O);
            for (int i = E - 1; i >= 0; i--)
            {
                Z[i] = Z[i + 1] * Z[i + 1];
                InvZ[i] = InvZ[i + 1] * InvZ[i + 1];
            }
        }

        /// <summary>
        /// a,bからc[k]=Σ(a[i]*b[k-i])を求め、aに代入
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static void Convolution(ref ModInt[] a, ModInt[] b)
        {
            int sz = a.Length + b.Length - 1;
            int len = 1;
            int lg = 0;
            while (len < sz)
            {
                lg++;
                len *= 2;
            }

            Array.Resize(ref a, len);
            Transform(a, lg, len, false);
            Array.Resize(ref b, len);
            Transform(b, lg, len, false);
            for (int i = 0; i < len; i++)
            {
                a[i] *= b[i];
            }

            Transform(a, lg, len, true);
            var invLen = ModInt.Inverse(len);
            for (int i = 0; i < sz; i++)
            {
                a[i] *= invLen;
            }

            Array.Resize(ref a, sz);
        }

        public static void Transform(ModInt[] a, int lg, int len, bool inverse)
        {
            for (int i = 0; i < len; i++)
            {
                int rev = BitsReverse(i, lg);
                if (i < rev)
                {
                    var tmp = a[i];
                    a[i] = a[rev];
                    a[rev] = tmp;
                }
            }

            for (int i = 1; i <= lg; i++)
            {
                int b = 1 << (i - 1);
                ModInt z = 1;
                for (int j = 0; j < b; j++)
                {
                    for (int k = j; k < len; k += (b << 1))
                    {
                        ModInt t = z * a[k + b];
                        ModInt u = a[k];
                        a[k] = u + t;
                        a[k + b] = u - t;
                    }

                    z *= inverse ? InvZ[i] : Z[i];
                }
            }
        }

        /// <summary>
        /// lg桁のbit列vのbitを逆順にする
        /// </summary>
        /// <param name="v"></param>
        /// <param name="lg"></param>
        /// <returns></returns>
        public static int BitsReverse(int v, int lg)
        {
            int result = 0;
            for (int i = 0; i < lg; i++)
            {
                if (((1 << i) & v) > 0)
                {
                    result |= (1 << (lg - 1 - i));
                }
            }

            return result;
        }

        /// <summary>
        /// a,bからc[k]=Σ(a[i]*b[k-i])を求める
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static double[] Convolution(double[] a, double[] b)
        {
            int n = 1;
            while (n < (a.Length + b.Length))
            {
                n *= 2;
            }

            var compA = new Complex[n];
            for (int i = 0; i < a.Length; i++)
            {
                compA[i] = new Complex(a[i], 0);
            }

            var compB = new Complex[n];
            for (int i = 0; i < b.Length; i++)
            {
                compB[i] = new Complex(b[i], 0);
            }

            Complex[] hatG = Transform(compA);
            Complex[] hatH = Transform(compB);
            Complex[] hatF = new Complex[n];
            for (int i = 0; i < n; i++)
            {
                hatF[i] = hatG[i] * hatH[i];
            }

            Complex[] nF = Transform(hatF, true);

            double[] f = new double[n];
            for (int i = 0; i < n; i++)
            {
                f[i] = nF[i].Real / n;
            }

            return f;
        }

        public static Complex[] DiscreteFourierTransform(Complex[] a, bool inverse = false)
        {
            int n = 1;
            while (n < a.Length)
            {
                n *= 2;
            }

            Complex[] aa = new Complex[n];
            for (int i = 0; i < a.Length; i++)
            {
                aa[i] = a[i];
            }

            return Transform(aa, inverse);
        }

        // f(x) = Σa_i*x^i の離散フーリエ変換
        // deg(f)は2羃
        private static Complex[] Transform(Complex[] a, bool inverse = false)
        {
            int n = a.Length;
            if (n == 1)
            {
                return a;
            }

            Complex[] a0 = new Complex[n / 2];
            Complex[] a1 = new Complex[n / 2];
            for (int i = 0; i < n / 2; i++)
            {
                a0[i] = a[2 * i];
                a1[i] = a[2 * i + 1];
            }

            Complex[] hatF0 = Transform(a0, inverse);
            Complex[] hatF1 = Transform(a1, inverse);

            // ζ_n
            Complex zetaN = Zeta(inverse ? -n : n);

            var result = new Complex[n];

            // ζ_n^i
            Complex zetaNi = 1;
            for (int i = 0; i < n; i++)
            {
                result[i] = hatF0[i % (n / 2)] + zetaNi * hatF1[i % (n / 2)];
                zetaNi *= zetaN;
            }

            return result;
        }

        private static Complex Zeta(int n)
        {
            double rad = 2 * Math.PI / n;
            double re = Math.Cos(rad);
            double im = Math.Sin(rad);
            return new Complex(re, im);
        }
    }
}

namespace CompLib.Mathematics
{
    public static partial class Combinatorics
    {
        private const int Max = 5000000;
        private static ModInt[] F;

        static Combinatorics()
        {
            F = new ModInt[Max + 1];
            F[0] = 1;
            for (int i = 1; i <= Max; i++)
            {
                F[i] = F[i - 1] * i;
            }
        }

        /// <summary>
        /// 階乗 n!
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public static ModInt Factorial(int n)
        {
            return F[n];
        }

        /// <summary>
        /// 順列
        /// </summary>
        /// <remarks>
        /// n個からm個選んで並べる
        /// </remarks>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static ModInt P(int n, int m)
        {
            return F[n] * ModInt.Inverse(F[n - m]);
        }

        /// <summary>
        /// 組み合わせ
        /// </summary>
        /// <remarks>
        /// n個からm個取り出す
        /// </remarks>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static ModInt C(int n, int m)
        {
            return F[n] * ModInt.Inverse(F[n - m] * F[m]);
        }

        /// <summary>
        /// 重複組み合わせ
        /// </summary>
        /// <remarks>
        /// n種類のものから重複を許して r個選ぶ
        /// </remarks>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public static ModInt H(int n, int r)
        {
            return C(n + r - 1, r);
        }
    }
}

// https://bitbucket.org/camypaper/complib
namespace CompLib.Mathematics
{
    #region ModInt

    /// <summary>
    /// [0,<see cref="Mod"/>) までの値を取るような数
    /// </summary>
    public struct ModInt
    {
        /// <summary>
        /// 剰余を取る値．
        /// </summary>
        // public const long Mod = (int) 1e9 + 7;
        public const long Mod = 998244353;

        /// <summary>
        /// 実際の数値．
        /// </summary>
        public long num;

        /// <summary>
        /// 値が <paramref name="n"/> であるようなインスタンスを構築します．
        /// </summary>
        /// <param name="n">インスタンスが持つ値</param>
        /// <remarks>パフォーマンスの問題上，コンストラクタ内では剰余を取りません．そのため，<paramref name="n"/> ∈ [0,<see cref="Mod"/>) を満たすような <paramref name="n"/> を渡してください．このコンストラクタは O(1) で実行されます．</remarks>
        public ModInt(long n)
        {
            num = n;
        }

        /// <summary>
        /// このインスタンスの数値を文字列に変換します．
        /// </summary>
        /// <returns>[0,<see cref="Mod"/>) の範囲内の整数を 10 進表記したもの．</returns>
        public override string ToString()
        {
            return num.ToString();
        }

        public static ModInt operator +(ModInt l, ModInt r)
        {
            l.num += r.num;
            if (l.num >= Mod) l.num -= Mod;
            return l;
        }

        public static ModInt operator -(ModInt l, ModInt r)
        {
            l.num -= r.num;
            if (l.num < 0) l.num += Mod;
            return l;
        }

        public static ModInt operator *(ModInt l, ModInt r)
        {
            return new ModInt(l.num * r.num % Mod);
        }

        public static implicit operator ModInt(long n)
        {
            n %= Mod;
            if (n < 0) n += Mod;
            return new ModInt(n);
        }

        /// <summary>
        /// 与えられた 2 つの数値からべき剰余を計算します．
        /// </summary>
        /// <param name="v">べき乗の底</param>
        /// <param name="k">べき指数</param>
        /// <returns>繰り返し二乗法により O(N log N) で実行されます．</returns>
        public static ModInt Pow(ModInt v, long k)
        {
            return Pow(v.num, k);
        }

        /// <summary>
        /// 与えられた 2 つの数値からべき剰余を計算します．
        /// </summary>
        /// <param name="v">べき乗の底</param>
        /// <param name="k">べき指数</param>
        /// <returns>繰り返し二乗法により O(N log N) で実行されます．</returns>
        public static ModInt Pow(long v, long k)
        {
            long ret = 1;
            for (k %= Mod - 1; k > 0; k >>= 1, v = v * v % Mod)
                if ((k & 1) == 1)
                    ret = ret * v % Mod;
            return new ModInt(ret);
        }

        /// <summary>
        /// 与えられた数の逆元を計算します．
        /// </summary>
        /// <param name="v">逆元を取る対象となる数</param>
        /// <returns>逆元となるような値</returns>
        /// <remarks>法が素数であることを仮定して，フェルマーの小定理に従って逆元を O(log N) で計算します．</remarks>
        public static ModInt Inverse(ModInt v)
        {
            return Pow(v, Mod - 2);
        }
    }

    #endregion

    #region Binomial Coefficient

    public class BinomialCoefficient
    {
        public ModInt[] fact, ifact;

        public BinomialCoefficient(int n)
        {
            fact = new ModInt[n + 1];
            ifact = new ModInt[n + 1];
            fact[0] = 1;
            for (int i = 1; i <= n; i++)
                fact[i] = fact[i - 1] * i;
            ifact[n] = ModInt.Inverse(fact[n]);
            for (int i = n - 1; i >= 0; i--)
                ifact[i] = ifact[i + 1] * (i + 1);
            ifact[0] = ifact[1];
        }

        public ModInt this[int n, int r]
        {
            get
            {
                if (n < 0 || n >= fact.Length || r < 0 || r > n) return 0;
                return fact[n] * ifact[n - r] * ifact[r];
            }
        }

        public ModInt RepeatedCombination(int n, int k)
        {
            if (k == 0) return 1;
            return this[n + k - 1, k];
        }
    }

    #endregion
}


namespace CompLib.Util
{
    using System;
    using System.Linq;

    class Scanner
    {
        private string[] _line;
        private int _index;
        private const char Separator = ' ';

        public Scanner()
        {
            _line = new string[0];
            _index = 0;
        }

        public string Next()
        {
            if (_index >= _line.Length)
            {
                string s;
                do
                {
                    s = Console.ReadLine();
                } while (s.Length == 0);

                _line = s.Split(Separator);
                _index = 0;
            }

            return _line[_index++];
        }

        public string ReadLine()
        {
            _index = _line.Length;
            return Console.ReadLine();
        }

        public int NextInt() => int.Parse(Next());
        public long NextLong() => long.Parse(Next());
        public double NextDouble() => double.Parse(Next());
        public decimal NextDecimal() => decimal.Parse(Next());
        public char NextChar() => Next()[0];
        public char[] NextCharArray() => Next().ToCharArray();

        public string[] Array()
        {
            string s = Console.ReadLine();
            _line = s.Length == 0 ? new string[0] : s.Split(Separator);
            _index = _line.Length;
            return _line;
        }

        public int[] IntArray() => Array().Select(int.Parse).ToArray();
        public long[] LongArray() => Array().Select(long.Parse).ToArray();
        public double[] DoubleArray() => Array().Select(double.Parse).ToArray();
        public decimal[] DecimalArray() => Array().Select(decimal.Parse).ToArray();
    }
}