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: Accepted)

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;
using System.Runtime.CompilerServices;
using System.Text;
using System.Diagnostics;
using System.Numerics;
using static System.Console;
using static System.Convert;
using static System.Math;
using static Template;
using SC = FastScanner;

public partial class Solver
{
    public void Solve()
    {
        ModInt.Build(100100);
        //二重階乗
        var G = new ModInt[100100];G[0] = 1;
        for (int i = 0; i < 100010; i++)
        {
            G[i + 1] = G[i] * (2 * (i + 1) - 1);
        }
        var N = sc.Int;
        var ct = new int[100100];
        for (int i = 0; i < N * 2; i++) ct[sc.Int]++;
        var deq = new Deque<ModInt[]>();
        for (int i = 0; i < ct.Length; i++)
        {
            if (ct[i] == 0) continue;
            ModInt s = 1;var C = new ModInt[ct[i] / 2 + 1];
            for(int j=0;j<=ct[i]/2;j++)
            {
                //iを用いて、身長が等しいペアをj組作る場合の数
                C[j] = s * ModInt.Comb(ct[i],2*j) * G[j];
                s = -s;
            }
            deq.PushHead(C);
        }
        //要素間のマージ
        while (deq.Count > 1)
        {
            //ある要素が次にみられるのは|deq|回後で|deq|は半分になっていくので各要素についてO(logN)回
            deq.PushTail(NTT.Convlution(deq.PopHead(), deq.PopHead()));
        }
        ModInt res = 0;
        //包除原理
        for (int i = 0; i < deq[0].Length; i++)
        {
            //身長が等しいペアをi組つくる場合の数*残りのペアの数
            res += G[N - i] * deq[0][i];
        }
        Console.WriteLine(res);
    }
}

public struct ModInt
{
    //public const long MOD = (int)1e9 + 7;
    public const long MOD = 998244353;
    public long Value { get; set; }
    public ModInt(long n = 0) { Value = n; }
    private static ModInt[] fac, inv, facinv;
    public override string ToString() => Value.ToString();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt operator +(ModInt l, ModInt r)
    {
        l.Value += r.Value;
        if (l.Value >= MOD) l.Value -= MOD;
        return l;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt operator -(ModInt l, ModInt r)
    {
        l.Value -= r.Value;
        if (l.Value < 0) l.Value += MOD;
        return l;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt operator *(ModInt l, ModInt r) => new ModInt(l.Value * r.Value % MOD);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt operator /(ModInt l, ModInt r) => l * Pow(r, MOD - 2);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator long(ModInt l) => l.Value;
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static implicit operator ModInt(long n)
    {
        n %= MOD; if (n < 0) n += MOD;
        return new ModInt(n);
    }

    public static ModInt Pow(ModInt m, long n)
    {
        if (n == 0) return 1;
        if (n % 2 == 0) return Pow(m * m, n >> 1);
        else return Pow(m * m, n >> 1) * m;
    }

    public static void Build(int n)
    {
        fac = new ModInt[n + 1];
        facinv = new ModInt[n + 1];
        inv = new ModInt[n + 1];
        inv[1] = fac[0] = fac[1] = facinv[0] = facinv[1] = 1;
        for (var i = 2; i <= n; i++)
        {
            fac[i] = fac[i - 1] * i;
            inv[i] = MOD - inv[MOD % i] * (MOD / i);
            facinv[i] = facinv[i - 1] * inv[i];
        }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt Fac(int n) => fac[n];
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt Inv(int n) => inv[n];
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt FacInv(int n) => facinv[n];
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ModInt Comb(int n, int r)
    {
        if (n < r) return 0;
        if (n == r) return 1;
        return fac[n] * facinv[r] * facinv[n - r];
    }
    public static ModInt Perm(int n, int r)
    {
        if (n < r) return 0;
        return fac[n] * facinv[n - r];
    }

}

public static class NTT
{
    const int g = 3;
    static ModInt[] sumE = null;
    static ModInt[] sumIE = null;
    static void butterfly(ModInt[] A)
    {
        int h = CeilPow2(A.Length);
        if (sumE == null)
        {
            sumE = new ModInt[30];
            ModInt[] es = new ModInt[30], ies = new ModInt[30];
            var cnt2 = CTZ((int)(ModInt.MOD - 1));
            var e = ModInt.Pow(g, (ModInt.MOD - 1) >> cnt2);
            var ie = 1 / e;
            for (int i = cnt2; i >= 2; i--)
            {
                es[i - 2] = e;
                ies[i - 2] = ie;
                e *= e;
                ie *= ie;
            }
            ModInt now = 1;
            for (int i = 0; i < cnt2 - 2; i++)
            {
                sumE[i] = es[i] * now;
                now *= ies[i];
            }
        }
        for (int ph = 1; ph <= h; ph++)
        {
            int w = 1 << (ph - 1), p = 1 << (h - ph);
            ModInt now = 1;
            for (int s = 0; s < w; s++)
            {
                int offset = s << (h - ph + 1);
                for (int i = 0; i < p; i++)
                {
                    var l = A[i + offset];
                    var r = A[i + offset + p] * now;
                    A[i + offset] = l + r;
                    A[i + offset + p] = l - r;
                }
                now *= sumE[CTZ(~s)];
            }
        }
    }
    static void butterflyInv(ModInt[] A)
    {
        int h = CeilPow2(A.Length);
        if (sumIE == null)
        {
            sumIE = new ModInt[30];
            ModInt[] es = new ModInt[30], ies = new ModInt[30];
            var cnt2 = CTZ((int)(ModInt.MOD - 1));
            var e = ModInt.Pow(g, (ModInt.MOD - 1) >> cnt2);
            var ie = 1 / e;
            for (int i = cnt2; i >= 2; i--)
            {
                es[i - 2] = e;
                ies[i - 2] = ie;
                e *= e;
                ie *= ie;
            }
            ModInt now = 1;
            for (int i = 0; i < cnt2 - 2; i++)
            {
                sumIE[i] = ies[i] * now;
                now *= es[i];
            }
        }
        for (int ph = h; ph >= 1; ph--)
        {
            int w = 1 << (ph - 1), p = 1 << (h - ph);
            ModInt inow = 1;
            for (int s = 0; s < w; s++)
            {
                int offset = s << (h - ph + 1);
                for (int i = 0; i < p; i++)
                {
                    var l = A[i + offset];
                    var r = A[i + offset + p];
                    A[i + offset] = l + r;
                    A[i + offset + p] =
                        (ModInt.MOD + l - r) * inow;
                }
                inow *= sumIE[CTZ(~s)];
            }
        }
    }
    public static ModInt[] Convlution(ModInt[] A, ModInt[] B)
    {
        int N = A.Length, M = B.Length;
        if (Min(N, M) <= 60)
        {
            if (N < M) swap(ref A, ref B);
            var res = new ModInt[N + M - 1];
            for (int i = 0; i < A.Length; i++)
                for (int j = 0; j < B.Length; j++)
                    res[i + j] += A[i] * B[j];
            return res;
        }
        var z = 1 << CeilPow2(N + M - 1);
        Array.Resize(ref A, z);
        Array.Resize(ref B, z);
        butterfly(A); butterfly(B);
        for (int i = 0; i < A.Length; i++)
            A[i] *= B[i];
        butterflyInv(A);
        Array.Resize(ref A, N + M - 1);
        var iz = (ModInt)1 / z;
        for (int i = 0; i < A.Length; i++) A[i] *= iz;
        return A;
    }
    static int CTZ(int v) => MSB(v & -v);
    static int MSB(int v)
    {
        var rt = 0;
        if ((v >> 16) > 0) { rt |= 16; v >>= 16; }
        if ((v >> 8) > 0) { rt |= 8; v >>= 8; }
        if ((v >> 4) > 0) { rt |= 4; v >>= 4; }
        if ((v >> 2) > 0) { rt |= 2; v >>= 2; }
        return rt | (v >> 1);
    }
    static int CeilPow2(int N) { int i = 0; while ((1 << i) < N) i++; return i; }
}

public class Deque<T>
{
    private T[] data;
    int offset, sz;
    public int Count { get; private set; }
    public Deque() { data = new T[sz = 16]; }
    public T this[int i]
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return data[MOD(i + offset)]; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        set { data[MOD(i + offset)] = value; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public int MOD(int v) => v & (sz - 1);
    private void Extend()
    {
        var t = new T[sz << 1];
        for (var i = 0; i < sz; i++)
            t[i] = data[MOD(offset + i)];
        offset = 0;
        data = t;
        sz <<= 1;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void PushHead(T item)
    {
        if (Count == sz) Extend();
        data[MOD(--offset)] = item;
        Count++;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public T PopHead()
    {
        if (Count == 0) return default(T);
        Count--;
        return data[MOD(offset++)];
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void PushTail(T item)
    {
        if (Count == sz) Extend();
        data[MOD(Count++ + offset)] = item;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public T PopTail()
    {
        if (Count == 0) return default(T);
        return data[MOD(--Count + offset)];
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool Any() => Count > 0;
    public T[] ToArray()
    {
        var rt = new T[Count];
        for (int i = 0; i < Count; i++)
            rt[i] = this[i];
        return rt;
    }
}

public class FastScanner
{
    //0x0a:LF 0x20:SPC 0x2d:- 0x30 ~ 0x39: 0 ~ 9
    static System.IO.Stream str = System.Console.OpenStandardInput();
    const int size = 1 << 10;
    static byte[] buffer = new byte[size];
    static int ptr;
    static int len;
    public int Int
    {
        get
        {
            var c = Read();
            while (c < 0x21)
            {
                c = Read();
            }
            var n = false;
            if (c == '-')
            {
                n = true;
                c = Read();
            }
            var ret = 0;
            while (c >= 0x30)
            {
                ret = ret * 10 + (c & 15);
                c = Read();
            }
            return n ? -ret : ret;
        }
    }
    public long Long
    {
        get
        {
            var c = Read();
            while (c < 0x21)
            {
                c = Read();
            }
            var n = false;
            if (c == '-')
            {
                n = true;
                c = Read();
            }
            var ret = 0L;
            while (c >= 0x30)
            {
                ret = ret * 10 + (c & 15);
                c = Read();
            }
            return n ? -ret : ret;
        }
    }
    byte Read()
    {
        if (ptr == len)
        {
            len = str.Read(buffer, 0, size);
            if (len == 0) return 0;
            ptr = 0;
        }
        return buffer[ptr++];
    }
}
#region Template
public partial class Solver
{
    public SC sc = new SC();
    static void Main(string[] args)
    {
        Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false });
        var sol = new Solver();
        int testcase = 1;
        //testcase = sol.sc.Int;
        //var th = new Thread(sol.Solve, 1 << 26);th.Start();th.Join();
        while (testcase-- > 0)
            sol.Solve();
        Console.Out.Flush();
    }
}
public static class Template
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool chmin<T>(ref T a, T b) where T : IComparable<T> { if (a.CompareTo(b) > 0) { a = b; return true; } return false; }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool chmax<T>(ref T a, T b) where T : IComparable<T> { if (a.CompareTo(b) < 0) { a = b; return true; } return false; }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void swap<T>(ref T a, ref T b) { var t = b; b = a; a = t; }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void swap<T>(this IList<T> A, int i, int j) { var t = A[i]; A[i] = A[j]; A[j] = t; }
    public static T[] Create<T>(int n, Func<T> f) { var rt = new T[n]; for (var i = 0; i < rt.Length; ++i) rt[i] = f(); return rt; }
    public static T[] Create<T>(int n, Func<int, T> f) { var rt = new T[n]; for (var i = 0; i < rt.Length; ++i) rt[i] = f(i); return rt; }
    public static void Out<T>(this IList<T> A, out T a) => a = A[0];
    public static void Out<T>(this IList<T> A, out T a, out T b) { a = A[0]; b = A[1]; }
    public static void Out<T>(this IList<T> A, out T a, out T b, out T c) { A.Out(out a, out b); c = A[2]; }
    public static void Out<T>(this IList<T> A, out T a, out T b, out T c, out T d) { A.Out(out a, out b, out c); d = A[3]; }
    public static string Concat<T>(this IEnumerable<T> A, string sp) => string.Join(sp, A);
    public static char ToChar(this int s, char begin = '0') => (char)(s + begin);
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> A) => A.OrderBy(v => Guid.NewGuid());
    public static int CompareTo<T>(this T[] A, T[] B, Comparison<T> cmp = null) { cmp = cmp ?? Comparer<T>.Default.Compare; for (var i = 0; i < Min(A.Length, B.Length); i++) { int c = cmp(A[i], B[i]); if (c > 0) return 1; else if (c < 0) return -1; } if (A.Length == B.Length) return 0; if (A.Length > B.Length) return 1; else return -1; }
    public static string ToStr<T>(this T[][] A) => A.Select(a => a.Concat(" ")).Concat("\n");
    public static int ArgMax<T>(this IList<T> A, Comparison<T> cmp = null) { cmp = cmp ?? Comparer<T>.Default.Compare; T max = A[0]; int rt = 0; for (int i = 1; i < A.Count; i++) if (cmp(max, A[i]) < 0) { max = A[i]; rt = i; } return rt; }
    public static T PopBack<T>(this List<T> A) { var v = A[A.Count - 1]; A.RemoveAt(A.Count - 1); return v; }
    public static void Fail<T>(T s) { Console.WriteLine(s); Console.Out.Close(); Environment.Exit(0); }
}
public class Scanner
{
    public string Str => Console.ReadLine().Trim();
    public int Int => int.Parse(Str);
    public long Long => long.Parse(Str);
    public double Double => double.Parse(Str);
    public int[] ArrInt => Str.Split(' ').Select(int.Parse).ToArray();
    public long[] ArrLong => Str.Split(' ').Select(long.Parse).ToArray();
    public char[][] Grid(int n) => Create(n, () => Str.ToCharArray());
    public int[] ArrInt1D(int n) => Create(n, () => Int);
    public long[] ArrLong1D(int n) => Create(n, () => Long);
    public int[][] ArrInt2D(int n) => Create(n, () => ArrInt);
    public long[][] ArrLong2D(int n) => Create(n, () => ArrLong);
    private Queue<string> q = new Queue<string>();
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public T Next<T>() { if (q.Count == 0) foreach (var item in Str.Split(' ')) q.Enqueue(item); return (T)Convert.ChangeType(q.Dequeue(), typeof(T)); }
    public void Make<T1>(out T1 v1) => v1 = Next<T1>();
    public void Make<T1, T2>(out T1 v1, out T2 v2) { v1 = Next<T1>(); v2 = Next<T2>(); }
    public void Make<T1, T2, T3>(out T1 v1, out T2 v2, out T3 v3) { Make(out v1, out v2); v3 = Next<T3>(); }
    public void Make<T1, T2, T3, T4>(out T1 v1, out T2 v2, out T3 v3, out T4 v4) { Make(out v1, out v2, out v3); v4 = Next<T4>(); }
    public void Make<T1, T2, T3, T4, T5>(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5) { Make(out v1, out v2, out v3, out v4); v5 = Next<T5>(); }
    public void Make<T1, T2, T3, T4, T5, T6>(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5, out T6 v6) { Make(out v1, out v2, out v3, out v4, out v5); v6 = Next<T6>(); }
    public void Make<T1, T2, T3, T4, T5, T6, T7>(out T1 v1, out T2 v2, out T3 v3, out T4 v4, out T5 v5, out T6 v6, out T7 v7) { Make(out v1, out v2, out v3, out v4, out v5, out v6); v7 = Next<T7>(); }
    public (T1, T2) Make<T1, T2>() { Make(out T1 v1, out T2 v2); return (v1, v2); }
    public (T1, T2, T3) Make<T1, T2, T3>() { Make(out T1 v1, out T2 v2, out T3 v3); return (v1, v2, v3); }
    public (T1, T2, T3, T4) Make<T1, T2, T3, T4>() { Make(out T1 v1, out T2 v2, out T3 v3, out T4 v4); return (v1, v2, v3, v4); }
}

#endregion