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: Wrong Answer)

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static System.Math;
using System.Text;
using System.Threading;
using System.Globalization;
using System.Runtime.CompilerServices;
using Library;

namespace Program
{
    public static class ProblemF
    {
        static bool SAIKI = false;
        static public int numberOfRandomCases = 0;
        static public void MakeTestCase(List<string> _input, List<string> _output, ref Func<string[], bool> _outputChecker)
        {
        }
        static public void Solve()
        {
            var N = NN;
            var hList = NNList(2 * N);
            var ary = new long[100001];
            foreach (var item in hList)
            {
                ary[item]++;
            }
            LIB_Mod._mod = 998244353;
            var calcPair = new long[2 * N + 1];
            calcPair[0] = calcPair[1] = 0;
            calcPair[2] = 1;
            for (var i = 3; i <= 2 * N; i++)
            {
                if (i % 2 == 1) calcPair[i] = calcPair[i - 1];
                else calcPair[i] = calcPair[i - 1] * (i - 1) % 998244353;
            }
            var allPat = calcPair[2 * N];
            var cnt = new List<long>[100001];
            for (var i = 0; i <= 100000; i++)
            {
                cnt[i] = new List<long>();
                cnt[i].Add(1);
                for (var j = 1; j <= ary[i] / 2; j++)
                {
                    cnt[i].Add(LIB_Mod.Comb(ary[i], j * 2) * calcPair[j * 2]);
                }
            }
            var q = new LIB_PriorityQueue<int, long[]>();
            foreach (var item in cnt) q.Push(item.Count, item.ToArray());
            while (q.Count > 1)
            {
                var a = q.Pop();
                var b = q.Pop();
                var c = LIB_NTT.Multiply(a.Value, b.Value);
                q.Push(c.Length, c);
            }
            var ary2 = q.Pop().Value;
            for (var i = 1; i < ary2.Length; i++)
            {
                LIB_Mod sign = -1;
                if (i % 2 == 0) sign = 1;
                allPat += sign * ary2[i] * calcPair[2 * N - i * 2];
                allPat %= 998244353;
            }
            Console.WriteLine(allPat);
        }
        class Printer : StreamWriter
        {
            public override IFormatProvider FormatProvider { get { return CultureInfo.InvariantCulture; } }
            public Printer(Stream stream) : base(stream, new UTF8Encoding(false, true)) { base.AutoFlush = false; }
            public Printer(Stream stream, Encoding encoding) : base(stream, encoding) { base.AutoFlush = false; }
        }
        static LIB_FastIO fastio = new LIB_FastIODebug();
        static public void Main(string[] args) { if (args.Length == 0) { fastio = new LIB_FastIO(); Console.SetOut(new Printer(Console.OpenStandardOutput())); } if (SAIKI) { var t = new Thread(Solve, 134217728); t.Start(); t.Join(); } else Solve(); Console.Out.Flush(); }
        static long NN => fastio.Long();
        static double ND => fastio.Double();
        static string NS => fastio.Scan();
        static long[] NNList(long N) => Repeat(0, N).Select(_ => NN).ToArray();
        static double[] NDList(long N) => Repeat(0, N).Select(_ => ND).ToArray();
        static string[] NSList(long N) => Repeat(0, N).Select(_ => NS).ToArray();
        static long Count<T>(this IEnumerable<T> x, Func<T, bool> pred) => Enumerable.Count(x, pred);
        static IEnumerable<T> Repeat<T>(T v, long n) => Enumerable.Repeat<T>(v, (int)n);
        static IEnumerable<int> Range(long s, long c) => Enumerable.Range((int)s, (int)c);
        static IOrderedEnumerable<T> OrderByRand<T>(this IEnumerable<T> x) => Enumerable.OrderBy(x, _ => xorshift);
        static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> x) => Enumerable.OrderBy(x.OrderByRand(), e => e);
        static IOrderedEnumerable<T1> OrderBy<T1, T2>(this IEnumerable<T1> x, Func<T1, T2> selector) => Enumerable.OrderBy(x.OrderByRand(), selector);
        static IOrderedEnumerable<T> OrderByDescending<T>(this IEnumerable<T> x) => Enumerable.OrderByDescending(x.OrderByRand(), e => e);
        static IOrderedEnumerable<T1> OrderByDescending<T1, T2>(this IEnumerable<T1> x, Func<T1, T2> selector) => Enumerable.OrderByDescending(x.OrderByRand(), selector);
        static IOrderedEnumerable<string> OrderBy(this IEnumerable<string> x) => x.OrderByRand().OrderBy(e => e, StringComparer.OrdinalIgnoreCase);
        static IOrderedEnumerable<T> OrderBy<T>(this IEnumerable<T> x, Func<T, string> selector) => x.OrderByRand().OrderBy(selector, StringComparer.OrdinalIgnoreCase);
        static IOrderedEnumerable<string> OrderByDescending(this IEnumerable<string> x) => x.OrderByRand().OrderByDescending(e => e, StringComparer.OrdinalIgnoreCase);
        static IOrderedEnumerable<T> OrderByDescending<T>(this IEnumerable<T> x, Func<T, string> selector) => x.OrderByRand().OrderByDescending(selector, StringComparer.OrdinalIgnoreCase);
        static string Join<T>(this IEnumerable<T> x, string separator = "") => string.Join(separator, x);
        static uint xorshift { get { _xsi.MoveNext(); return _xsi.Current; } }
        static IEnumerator<uint> _xsi = _xsc();
        static IEnumerator<uint> _xsc() { uint x = 123456789, y = 362436069, z = 521288629, w = (uint)(DateTime.Now.Ticks & 0xffffffff); while (true) { var t = x ^ (x << 11); x = y; y = z; z = w; w = (w ^ (w >> 19)) ^ (t ^ (t >> 8)); yield return w; } }
        static bool Chmax<T>(this ref T lhs, T rhs) where T : struct, IComparable<T> { if (lhs.CompareTo(rhs) < 0) { lhs = rhs; return true; } return false; }
        static bool Chmin<T>(this ref T lhs, T rhs) where T : struct, IComparable<T> { if (lhs.CompareTo(rhs) > 0) { lhs = rhs; return true; } return false; }
    }
}
namespace Library {
    class LIB_PriorityQueue
    {
        long[] heap;
        int[] dat;
        public long Count
        {
            get;
            private set;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue()
        {
            heap = new long[16384];
            dat = new int[16384];
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Push(long key, int val)
        {
            if (Count == heap.Length) Expand();
            var i = Count++;
            heap[i] = key;
            dat[i] = val;
            while (i > 0)
            {
                var ni = (i - 1) / 2;
                if (key >= heap[ni]) break;
                heap[i] = heap[ni];
                dat[i] = dat[ni];
                i = ni;
            }
            heap[i] = key;
            dat[i] = val;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public KeyValuePair<long, int> Pop()
        {
            var ret = new KeyValuePair<long, int>(heap[0], dat[0]);
            var key = heap[--Count];
            var val = dat[Count];
            if (Count == 0) return ret;
            var i = 0; while ((i << 1) + 1 < Count)
            {
                var i1 = (i << 1) + 1;
                var i2 = (i << 1) + 2;
                if (i2 < Count && heap[i1] > heap[i2]) i1 = i2;
                if (key <= heap[i1]) break;
                heap[i] = heap[i1];
                dat[i] = dat[i1];
                i = i1;
            }
            heap[i] = key;
            dat[i] = val;
            return ret;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void Expand()
        {
            var tmp = new long[Count << 1];
            for (var i = 0; i < heap.Length; ++i) tmp[i] = heap[i];
            heap = tmp;
            var tmp2 = new int[Count << 1];
            for (var i = 0; i < dat.Length; ++i) tmp2[i] = dat[i];
            dat = tmp2;
        }
    }
    class LIB_PriorityQueue<T>
    {
        T[] heap;
        Comparison<T> comp;
        public T Peek => heap[0];
        public long Count
        {
            get;
            private set;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(long cap, Comparison<T> cmp, bool asc = true)
        {
            heap = new T[cap];
            comp = asc ? cmp : (x, y) => cmp(y, x);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(Comparison<T> cmp, bool asc = true)
        {
            heap = new T[16384];
            comp = asc ? cmp : (x, y) => cmp(y, x);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(long cap, bool asc = true) : this(cap, Comparer<T>.Default.Compare, asc) { }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(bool asc = true) : this(Comparer<T>.Default.Compare, asc) { }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Push(T val)
        {
            if (Count == heap.Length) Expand();
            var i = Count++;
            heap[i] = val;
            while (i > 0)
            {
                var ni = (i - 1) / 2;
                if (comp(val, heap[ni]) >= 0) break;
                heap[i] = heap[ni];
                i = ni;
            }
            heap[i] = val;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public T Pop()
        {
            var ret = heap[0];
            var val = heap[--Count];
            if (Count == 0) return ret;
            var i = 0; while ((i << 1) + 1 < Count)
            {
                var i1 = (i << 1) + 1;
                var i2 = (i << 1) + 2;
                if (i2 < Count && comp(heap[i1], heap[i2]) > 0) i1 = i2;
                if (comp(val, heap[i1]) <= 0) break;
                heap[i] = heap[i1]; i = i1;
            }
            heap[i] = val;
            return ret;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void Expand()
        {
            var tmp = new T[Count << 1];
            for (var i = 0; i < heap.Length; ++i) tmp[i] = heap[i];
            heap = tmp;
        }
    }
    class LIB_PriorityQueue<TK, TV>
    {
        LIB_PriorityQueue<KeyValuePair<TK, TV>> q;
        public KeyValuePair<TK, TV> Peek => q.Peek;
        public long Count => q.Count;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(long cap, Comparison<TK> cmp, bool asc = true)
        {
            q = new LIB_PriorityQueue<KeyValuePair<TK, TV>>(cap, (x, y) => cmp(x.Key, y.Key), asc);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(Comparison<TK> cmp, bool asc = true)
        {
            q = new LIB_PriorityQueue<KeyValuePair<TK, TV>>((x, y) => cmp(x.Key, y.Key), asc);
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(long cap, bool asc = true) : this(cap, Comparer<TK>.Default.Compare, asc) { }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_PriorityQueue(bool asc = true) : this(Comparer<TK>.Default.Compare, asc) { }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Push(TK k, TV v) => q.Push(new KeyValuePair<TK, TV>(k, v));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public KeyValuePair<TK, TV> Pop() => q.Pop();
    }
    class LIB_FastIO
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_FastIO() { str = Console.OpenStandardInput(); }
        readonly Stream str;
        readonly byte[] buf = new byte[2048];
        int len, ptr;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        byte read()
        {
            if (ptr >= len)
            {
                ptr = 0;
                if ((len = str.Read(buf, 0, 2048)) <= 0)
                {
                    return 0;
                }
            }
            return buf[ptr++];
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        char Char()
        {
            byte b = 0;
            do b = read();
            while (b < 33 || 126 < b);
            return (char)b;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        virtual public string Scan()
        {
            var sb = new StringBuilder();
            for (var b = Char(); b >= 33 && b <= 126; b = (char)read())
                sb.Append(b);
            return sb.ToString();
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        virtual public long Long()
        {
            long ret = 0; byte b = 0; var ng = false;
            do b = read();
            while (b != '-' && (b < '0' || '9' < b));
            if (b == '-') { ng = true; b = read(); }
            for (; true; b = read())
            {
                if (b < '0' || '9' < b)
                    return ng ? -ret : ret;
                else ret = (ret << 3) + (ret << 1) + b - '0';
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        virtual public double Double() { return double.Parse(Scan(), CultureInfo.InvariantCulture); }
    }
    class LIB_FastIODebug : LIB_FastIO
    {
        Queue<string> param = new Queue<string>();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        string NextString() { if (param.Count == 0) foreach (var item in Console.ReadLine().Split(' ')) param.Enqueue(item); return param.Dequeue(); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_FastIODebug() { }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override string Scan() => NextString();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override long Long() => long.Parse(NextString());
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override double Double() => double.Parse(NextString());
    }
    partial struct LIB_Mod : IEquatable<LIB_Mod>, IEquatable<long>
    {
        static public long _mod = 1000000007; long v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public LIB_Mod(long x)
        {
            if (x < _mod && x >= 0) v = x;
            else if ((v = x % _mod) < 0) v += _mod;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public implicit operator LIB_Mod(long x) => new LIB_Mod(x);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public implicit operator long(LIB_Mod x) => x.v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Add(LIB_Mod x) { if ((v += x.v) >= _mod) v -= _mod; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Sub(LIB_Mod x) { if ((v -= x.v) < 0) v += _mod; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Mul(LIB_Mod x) => v = (v * x.v) % _mod;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public void Div(LIB_Mod x) => v = (v * Inverse(x.v)) % _mod;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod operator +(LIB_Mod x, LIB_Mod y) { var t = x.v + y.v; return t >= _mod ? new LIB_Mod { v = t - _mod } : new LIB_Mod { v = t }; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod operator -(LIB_Mod x, LIB_Mod y) { var t = x.v - y.v; return t < 0 ? new LIB_Mod { v = t + _mod } : new LIB_Mod { v = t }; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod operator *(LIB_Mod x, LIB_Mod y) => x.v * y.v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod operator /(LIB_Mod x, LIB_Mod y) => x.v * Inverse(y.v);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public bool operator ==(LIB_Mod x, LIB_Mod y) => x.v == y.v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public bool operator !=(LIB_Mod x, LIB_Mod y) => x.v != y.v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public long Inverse(long x)
        {
            long b = _mod, r = 1, u = 0, t = 0;
            while (b > 0)
            {
                var q = x / b;
                t = u;
                u = r - q * u;
                r = t;
                t = b;
                b = x - q * b;
                x = t;
            }
            return r < 0 ? r + _mod : r;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(LIB_Mod x) => v == x.v;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool Equals(long x) => v == x;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override bool Equals(object x) => x == null ? false : Equals((LIB_Mod)x);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override int GetHashCode() => v.GetHashCode();
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public override string ToString() => v.ToString();
        static List<LIB_Mod> _fact = new List<LIB_Mod>() { 1 };
        static long _factm = _mod;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void B(long n)
        {
            if (_factm != _mod) _fact = new List<LIB_Mod>() { 1 };
            if (n >= _fact.Count)
                for (int i = _fact.Count; i <= n; ++i)
                    _fact.Add(_fact[i - 1] * i);
            _factm = _mod;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod Comb(long n, long k)
        {
            B(n);
            if (n == 0 && k == 0) return 1;
            if (n < k || n < 0) return 0;
            return _fact[(int)n] / _fact[(int)(n - k)] / _fact[(int)k];
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod CombOK(long n, long k)
        {
            LIB_Mod ret = 1;
            for (var i = 0; i < k; i++) ret *= n - i;
            for (var i = 1; i <= k; i++) ret /= i;
            return ret;
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod Perm(long n, long k)
        {
            B(n);
            if (n == 0 && k == 0) return 1;
            if (n < k || n < 0) return 0;
            return _fact[(int)n] / _fact[(int)(n - k)];
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod KanzenPerm(long n) => Enumerable.Range(0, (int)n + 1).Aggregate((LIB_Mod)0, (a, e) => a + (1 - ((e & 1) << 1)) * LIB_Mod.Comb(n, e) * LIB_Mod.Perm(n - e, n - e));
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public LIB_Mod Pow(LIB_Mod x, long y)
        {
            LIB_Mod a = 1;
            while (y != 0)
            {
                if ((y & 1) == 1) a.Mul(x);
                x.Mul(x);
                y >>= 1;
            }
            return a;
        }
    }
    class LIB_NTT
    {
        const uint mod1 = 1045430273;
        const uint root1 = 3;
        const uint mod2 = 1051721729;
        const uint root2 = 6;
        const uint mod3 = 1053818881;
        const uint root3 = 7;
        const uint mod4 = 998244353;
        const uint root4 = 3;
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint mul1(uint x, uint y) => (uint)((ulong)x * y % mod1);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint mul2(uint x, uint y) => (uint)((ulong)x * y % mod2);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint mul3(uint x, uint y) => (uint)((ulong)x * y % mod3);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint mul4(uint x, uint y) => (uint)((ulong)x * y % mod4);
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint pow1(uint x, uint n) { uint res = 1; while (n > 0) { if ((n & 1) != 0) res = mul1(res, x); x = mul1(x, x); n >>= 1; } return res; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint pow2(uint x, uint n) { uint res = 1; while (n > 0) { if ((n & 1) != 0) res = mul2(res, x); x = mul2(x, x); n >>= 1; } return res; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint pow3(uint x, uint n) { uint res = 1; while (n > 0) { if ((n & 1) != 0) res = mul3(res, x); x = mul3(x, x); n >>= 1; } return res; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint pow4(uint x, uint n) { uint res = 1; while (n > 0) { if ((n & 1) != 0) res = mul4(res, x); x = mul4(x, x); n >>= 1; } return res; }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint inverse1(uint x) { long b = mod1, r = 1, u = 0, t = 0, x2 = x; while (b > 0) { var q = x2 / b; t = u; u = r - q * u; r = t; t = b; b = x2 - q * b; x2 = t; } return (uint)(r < 0 ? r + mod1 : r); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint inverse2(uint x) { long b = mod2, r = 1, u = 0, t = 0, x2 = x; while (b > 0) { var q = x2 / b; t = u; u = r - q * u; r = t; t = b; b = x2 - q * b; x2 = t; } return (uint)(r < 0 ? r + mod2 : r); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint inverse3(uint x) { long b = mod3, r = 1, u = 0, t = 0, x2 = x; while (b > 0) { var q = x2 / b; t = u; u = r - q * u; r = t; t = b; b = x2 - q * b; x2 = t; } return (uint)(r < 0 ? r + mod3 : r); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint inverse4(uint x) { long b = mod4, r = 1, u = 0, t = 0, x2 = x; while (b > 0) { var q = x2 / b; t = u; u = r - q * u; r = t; t = b; b = x2 - q * b; x2 = t; } return (uint)(r < 0 ? r + mod4 : r); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void ntt1(ref uint[] a, bool rev = false) { if (a.Length == 1) return; var halfn = (uint)a.Length >> 1; var b = new uint[a.Length]; var s = pow1(root1, rev ? (mod1 - 1 - (mod1 - 1) / (uint)a.Length) : (mod1 - 1) / (uint)a.Length); uint i, j, k, l, r; var kp = new uint[halfn + 1]; kp[0] = 1; for (i = 0; i < kp.Length - 1; ++i) kp[i + 1] = mul1(kp[i], s); for (i = 1, l = halfn; i < a.Length; i <<= 1, l >>= 1) { for (j = 0, r = 0; j < l; ++j, r += i) { for (k = 0, s = kp[i * j]; k < i; ++k) { var kr = k + r; var krhalfn = kr + halfn; b[kr + r] = (a[kr] + a[krhalfn]) % mod1; b[kr + r + i] = (uint)(((ulong)(a[kr] + mod1 - a[krhalfn]) * s) % mod1); } } var t = a; a = b; b = t; } if (rev) { s = inverse1((uint)a.Length); for (i = 0; i < a.Length; ++i) a[i] = mul1(a[i], s); } }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void ntt2(ref uint[] a, bool rev = false) { if (a.Length == 1) return; var halfn = (uint)a.Length >> 1; var b = new uint[a.Length]; var s = pow2(root2, rev ? (mod2 - 1 - (mod2 - 1) / (uint)a.Length) : (mod2 - 1) / (uint)a.Length); uint i, j, k, l, r; var kp = new uint[halfn + 1]; kp[0] = 1; for (i = 0; i < kp.Length - 1; ++i) kp[i + 1] = mul2(kp[i], s); for (i = 1, l = halfn; i < a.Length; i <<= 1, l >>= 1) { for (j = 0, r = 0; j < l; ++j, r += i) { for (k = 0, s = kp[i * j]; k < i; ++k) { var kr = k + r; var krhalfn = kr + halfn; b[kr + r] = (a[kr] + a[krhalfn]) % mod2; b[kr + r + i] = (uint)(((ulong)(a[kr] + mod2 - a[krhalfn]) * s) % mod2); } } var t = a; a = b; b = t; } if (rev) { s = inverse2((uint)a.Length); for (i = 0; i < a.Length; ++i) a[i] = mul2(a[i], s); } }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void ntt3(ref uint[] a, bool rev = false) { if (a.Length == 1) return; var halfn = (uint)a.Length >> 1; var b = new uint[a.Length]; var s = pow3(root3, rev ? (mod3 - 1 - (mod3 - 1) / (uint)a.Length) : (mod3 - 1) / (uint)a.Length); uint i, j, k, l, r; var kp = new uint[halfn + 1]; kp[0] = 1; for (i = 0; i < kp.Length - 1; ++i) kp[i + 1] = mul3(kp[i], s); for (i = 1, l = halfn; i < a.Length; i <<= 1, l >>= 1) { for (j = 0, r = 0; j < l; ++j, r += i) { for (k = 0, s = kp[i * j]; k < i; ++k) { var kr = k + r; var krhalfn = kr + halfn; b[kr + r] = (a[kr] + a[krhalfn]) % mod3; b[kr + r + i] = (uint)(((ulong)(a[kr] + mod3 - a[krhalfn]) * s) % mod3); } } var t = a; a = b; b = t; } if (rev) { s = inverse3((uint)a.Length); for (i = 0; i < a.Length; ++i) a[i] = mul3(a[i], s); } }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static void ntt4(ref uint[] a, bool rev = false)
        {
            if (a.Length == 1) return;
            var halfn = (uint)a.Length >> 1;
            var b = new uint[a.Length];
            var s = pow4(root4, rev ? (mod4 - 1 - (mod4 - 1) / (uint)a.Length) : (mod4 - 1) / (uint)a.Length);
            uint i, j, k, l, r;
            var kp = new uint[halfn + 1];
            kp[0] = 1;
            for (i = 0; i < kp.Length - 1; ++i) kp[i + 1] = mul4(kp[i], s);
            for (i = 1, l = halfn; i < a.Length; i <<= 1, l >>= 1)
            {
                for (j = 0, r = 0; j < l; ++j, r += i)
                {
                    for (k = 0, s = kp[i * j]; k < i; ++k)
                    {
                        var kr = k + r;
                        var krhalfn = kr + halfn;
                        b[kr + r] = (a[kr] + a[krhalfn]) % mod4;
                        b[kr + r + i] = (uint)(((ulong)(a[kr] + mod4 - a[krhalfn]) * s) % mod4);
                    }
                }
                var t = a; a = b; b = t;
            }
            if (rev)
            {
                s = inverse4((uint)a.Length);
                for (i = 0; i < a.Length; ++i) a[i] = mul4(a[i], s);
            }
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint[] Multiply1(uint[] a, uint[] b) { var n = a.Length + b.Length - 1; var t = 1; while (t < n) t <<= 1; var na = new uint[t]; var nb = new uint[t]; for (var i = 0; i < a.Length; ++i) na[i] = a[i]; for (var i = 0; i < b.Length; ++i) nb[i] = b[i]; ntt1(ref na); ntt1(ref nb); for (var i = 0; i < t; ++i) na[i] = mul1(na[i], nb[i]); ntt1(ref na, true); return na.Take(n).ToArray(); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint[] Multiply2(uint[] a, uint[] b) { var n = a.Length + b.Length - 1; var t = 1; while (t < n) t <<= 1; var na = new uint[t]; var nb = new uint[t]; for (var i = 0; i < a.Length; ++i) na[i] = a[i]; for (var i = 0; i < b.Length; ++i) nb[i] = b[i]; ntt2(ref na); ntt2(ref nb); for (var i = 0; i < t; ++i) na[i] = mul2(na[i], nb[i]); ntt2(ref na, true); return na.Take(n).ToArray(); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static uint[] Multiply3(uint[] a, uint[] b) { var n = a.Length + b.Length - 1; var t = 1; while (t < n) t <<= 1; var na = new uint[t]; var nb = new uint[t]; for (var i = 0; i < a.Length; ++i) na[i] = a[i]; for (var i = 0; i < b.Length; ++i) nb[i] = b[i]; ntt3(ref na); ntt3(ref nb); for (var i = 0; i < t; ++i) na[i] = mul3(na[i], nb[i]); ntt3(ref na, true); return na.Take(n).ToArray(); }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public long[] Multiply(long[] a, long[] b)
        {
            var n = a.Length + b.Length - 1;
            var t = 1;
            while (t < n) t <<= 1;
            var na = new uint[t];
            var nb = new uint[t];
            for (var i = 0; i < a.Length; ++i) na[i] = (uint)a[i];
            for (var i = 0; i < b.Length; ++i) nb[i] = (uint)b[i];
            ntt4(ref na);
            ntt4(ref nb);
            for (var i = 0; i < t; ++i) na[i] = mul4(na[i], nb[i]);
            ntt4(ref na, true);
            return na.Take(n).Select(e => (long)e).ToArray();
        }
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static public long[] Multiply(long[] a, long[] b, long mod)
        {
            if (mod == 998244353) return Multiply(a, b);
            var uinta = a.Select(e => (uint)e).ToArray();
            var uintb = b.Select(e => (uint)e).ToArray();
            var x = Multiply1(uinta, uintb);
            var y = Multiply2(uinta, uintb);
            var z = Multiply3(uinta, uintb);
            var m1_inv_m2 = inverse2(mod1);
            var m12_inv_m3 = inverse3(mul3(mod1, mod2));
            var m12_mod = (long)mod1 * mod2 % mod;
            var ret = new long[x.Length];
            for (var i = 0; i < x.Length; ++i)
            {
                var v1 = mul2(y[i] + (mod2 << 1) - x[i], m1_inv_m2);
                var v2 = mul3(z[i] + (mod3 << 1) - x[i] + mod3 - mul3(mod1, v1), m12_inv_m3);
                ret[i] = (x[i] + ((long)mod1 * v1 % mod) + ((long)m12_mod * v2 % mod)) % mod;
            }
            return ret;
        }
    }
}