Problem:
Score : 100 points

Problem Statement

You are given two integer arrays a_0, a_1, ..., a_{N - 1} and b_0, b_1, ..., b_{M - 1}. Calculate the array c_0, c_1, ..., c_{(N - 1) + (M - 1)}, defined by c_i = \sum_{j = 0}^i a_j b_{i - j} \bmod 998244353.

Constraints

1 \leq N, M \leq 524288

0 \leq a_i, b_i < 998244353

All values in Input are integer.

Input

Input is given from Standard Input in the following format:

N M
a_0 a_1 ... a_{N-1}
b_0 b_1 ... b_{M-1}

Output

Print the answer in the following format:

c_0 c_1 ... c_{(N - 1) + (M - 1)}

Sample Input 1

4 5
1 2 3 4
5 6 7 8 9

Sample Output 1

5 16 34 60 70 70 59 36

Sample Input 2

1 1
10000000
10000000

Sample Output 2

871938225

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

using System;
using System.Collections.Generic;
public class Hello{
    public static void Main(){
        BigInt bi = new BigInt(1, 23);
        bi.modulo_list[0] = 998244353;
        bi.root_manual();
        bi.make_basis();
        var line = Console.ReadLine().Split(' ');
        int N = int.Parse(line[0]), M = int.Parse(line[1]);
        var A = Console.ReadLine().Split(' ');
        var B = Console.ReadLine().Split(' ');
        int pl, dpt;
        int[] line1 = bi.powlimit(N+M);
        pl = line1[0];
        dpt = line1[1];
        long[] X1 = new long[pl];
        long[] X2 = new long[pl];
        for (int i=0;i<N;i++){
            X1[i] = long.Parse(A[i]);
        }
        for (int i=0;i<M;i++){
            X2[i] = long.Parse(B[i]);
        }
        X1 = bi.NTT(X1, pl, 0, dpt);
        X2 = bi.NTT(X2, pl, 0, dpt);
        long[] Y = new long[pl];
        for (int i=0;i<pl;i++){
            Y[i] = X1[i]*X2[i]%998244353;
        }
        Y = bi.NTT(Y, pl, 0, dpt, true);
        for (int i=0;i<N+M-1;i++){
            if (i != 0){
                Console.Write(" ");
            }
            Console.Write(Y[i]);
        }
        Console.WriteLine();
    }
}
public class BigInt{
    private int cr;
    private int bl;
    public long[] modulo_list;
    public long[] primal_root_list;
    public long[] bin_list;
    public long[] primal_base_matrix;
    public long[] inverse_base_matrix;
    public BigInt(int convolution_rank, int binary_level){
        this.cr = convolution_rank;
        this.bl = binary_level;
        this.modulo_list = new long[convolution_rank];
        this.primal_root_list = new long[convolution_rank];
        this.bin_list = new long[binary_level+1];
        this.primal_base_matrix = new long[convolution_rank*(binary_level+1)];
        this.inverse_base_matrix = new long[convolution_rank*(binary_level+1)];
        this.bin_list[0] = 1;
        for (int i=0;i<binary_level;i++){
            this.bin_list[i+1] = 2 * this.bin_list[i];
        }
        for (int i=0;i<convolution_rank;i++){
            this.modulo_list[i] = 1;
            this.primal_root_list[i] = 1;
            for (int j=0;j<=binary_level;j++){
                this.primal_base_matrix[i*(binary_level+1)+j] = 0;
                this.inverse_base_matrix[i*(binary_level+1)+j] = 0;
            }
        }
    }
    public long doubling(long n, long m, long modulo=0){
        long y = 1;
        long bas = n;
        long tmp = m;
        while (tmp!=0){
            if (tmp % 2 == 1){
                y *= bas;
                if (modulo!=0){
                    y %= modulo;
                }
            }
            bas *= bas;
            if (modulo!=0){
                bas %= modulo;
            }
            tmp /= 2;
        }
        return y;
    }
    public int[] powlimit(long n){
        int[] a = new int[2];
        a[0] = 1;
        a[1] = 0;
        while (a[0] < n){
            a[0] *= 2;
            a[1] += 1;
        }
        return a;
    }
    public bool IsPrime(long num){
        long p = 2;
        if (num <= 1){
            return false;
        }
        while (p * p <= num){
            if (num % p == 0){
                return false;
            }
            p += 1;
        }
        return true;
    }
    private long safety_divisor(long a, long b, bool modulo=false){
        if (b == 0){
            Console.Error.WriteLine("ValueError: ZeroDivision");
        }
        if (a % b < 0){
            if (modulo){
                return b + a % b;
            }
            return a / b - 1;
        }
        if (modulo){
            return a % b;
        }
        return a / b;
    }
    public long[] extgcd(long a, long b, long c){
        if (b < 0){
            a = -a;
            b = -b;
            c = -c;
        }
        long x, y, u, v, k, l, tmp1, tmp2, tmp3;
        x = 1;
        y = 0;
        u = 0;
        v = 1;
        k = a;
        l = b;
        while (l!=0){
            tmp1 = x;
            tmp2 = y;
            tmp3 = k;
            x = u;
            y = v;
            u = tmp1 - u * safety_divisor(k, l);
            v = tmp2 - v * safety_divisor(k, l);
            k = l;
            l = safety_divisor(tmp3, l, true);
        }
        if (c % k != 0){
            Console.Error.WriteLine("No Solution");
        }
        long[] sol = new long[2];
        sol[0] = c*x;
        sol[1] = c*y;
        return sol;
    }
    public long inved(long a, long modulo){
        long[] x = extgcd(a, modulo, 1);
        return (x[0]+modulo)%modulo;
    }
    public void root_manual(){
        long r = 1;
        bool flg = true;
        bool fflg;
        for (int i=0;i<this.cr;i++){
            r = 1;
            flg = true;
            while (flg){
                fflg = true;
                for (int j=0;j<this.bl;j++){
                    if (doubling(r, this.bin_list[j], modulo_list[i]) == 1){
                        fflg = false;
                    }
                }
                if (doubling(r, this.bin_list[this.bl], modulo_list[i]) != 1){
                    fflg = false;
                }
                if (fflg){
                    flg = false;
                }
                else{
                    r += 1;
                    if (r >= modulo_list[i]){
                        break;
                    }
                }
            }
            this.primal_root_list[i] = r;
        }
    }
    public void make_prime_root(){
        int cnt = 0;
        long j = 1;
        long last = this.bin_list[this.bl];
        bool flg, fflg;
        long r;
        while (cnt < this.cr){
            if (IsPrime(j*last+1)){
                flg = true;
                r = 1;
                while (flg){
                    fflg = true;
                    for (int i=0;i<this.bl;i++){
                        if (doubling(r, this.bin_list[i], j*last+1) == 1){
                            fflg = false;
                        }
                    }
                    if (doubling(r, last, j*last+1) != 1){
                        fflg = false;
                    }
                    if (fflg){
                        flg = false;
                    }
                    else{
                        r += 1;
                        if (r >= j*last+1){
                            break;
                        }
                    }
                }
                if (!flg){
                    this.modulo_list[cnt] = j*last+1;
                    this.primal_root_list[cnt] = r;
                    cnt += 1;
                }
            }
            j += 2;
        }
    }
    public void make_basis(){
        long tmp;
        for (int i=0;i<this.cr;i++){
            for (int j=0;j<=this.bl;j++){
                tmp = doubling(2, this.bl-j);
                this.primal_base_matrix[i*(this.bl+1)+j] = doubling(primal_root_list[i], tmp, modulo_list[i]);
                this.inverse_base_matrix[i*(this.bl+1)+j] = inved(primal_base_matrix[i*(this.bl+1)+j], modulo_list[i]);
            }
        }
    }
    public long[] NTT(long[] f, int n, int idx, int depth, bool inverse=false){
        long[] res = new long[n];
        long[] tmp = new long[n];
        long MOD = this.modulo_list[idx];
        long ipl = inved(n, MOD);
        long bas;
        long pos;
        long grow, seed;
        for (int i=0;i<n;i++){
            bas = 1;
            pos = 0;
            for (int j=depth;j>0;j--){
                pos += bas * ((i>>(j-1))%2);
                bas *= 2;
            }
            res[i] = f[pos];
        }
        for (int i=0;i<depth;i++){
            grow = 1;
            if (inverse){
                seed = primal_base_matrix[idx*(this.bl+1)+i+1];
            }
            else{
                seed = inverse_base_matrix[idx*(this.bl+1)+1+i];
            }
            for (int k=0;k<(1<<i);k++){
                for (int j=0;j<(1<<(depth-i-1));j++){
                    tmp[j*(1<<(i+1))+k+0*(1<<i)] = (res[j*(1<<(i+1))+k] + (grow * res[j*(1<<(i+1))+k+(1<<i)]) % MOD) % MOD;
                    tmp[j*(1<<(i+1))+k+1*(1<<i)] = safety_divisor((res[j*(1<<(i+1))+k] - (grow * res[j*(1<<(i+1))+k+(1<<i)]) % MOD), MOD, true);
                }
                grow *= seed;
                grow %= MOD;
            }
            for (int j=0;j<n;j++){
                res[j] = tmp[j];
            }
        }
        if (inverse){
            for (int i=0;i<n;i++){
                res[i] *= ipl;
                res[i] %= MOD;
            }
        }
        return res;
    }
    public void WriteArray(long[] a){
        int n = a.Length;
        Console.Write("[");
        for (int i=0;i<n;i++){
            if (i != 0){
                Console.Write(", ");
            }
            Console.Write(a[i]);
        }
        Console.WriteLine("]");
    }
    private bool check_num(string s){
        int st = 0;
        int n = s.Length;
        bool chk = true;
        if (s[0] == '-'){
            st = 1;
        }
        if (n <= st){
            chk = false;
        }
        else{
            for (int i=st;i<n;i++){
                if (s[i] < 48 || s[i] > 57){
                    chk = false;
                }
            }
        }
        return chk;
    }
    public string Plus(string a, string b){
        bool sign_a = true, sign_b = true;
        int n = a.Length, m = b.Length;
        int sta, stb;
        if (a[0] == '-'){
            sta = 1;
            sign_a = false;
        }
        else{
            sta = 0;
        }
        if (b[0] == '-'){
            stb = 1;
            sign_b = false;
        }
        else{
            stb = 0;
        }
        bool chk_a = check_num(a), chk_b = check_num(b);
        if (!chk_a || !chk_b){
            if (!chk_a && chk_b){
                Console.Error.WriteLine("Invalid input: 1st argument");
            }
            if (chk_a && !chk_b){
                Console.Error.WriteLine("Invalid input: 2nd argument");
            }
            if (!chk_a && !chk_b){
                Console.Error.WriteLine("Invalid inputs: 1st and 2nd arguments");
            }
        }
        int lm = Math.Max(n, m);
        int[] dig_a = new int[lm+10];
        int[] dig_b = new int[lm+10];
        int[] sub = new int[lm+10];
        int[] ans = new int[lm+10];
        for (int i=n-1;i>=sta;i--){
            if (sign_a){
                dig_a[n-i-1] = a[i] - 48;
            }
            else{
                dig_a[n-i-1] = 57 - a[i];
            }
        }
        for (int i=m-1;i>=stb;i--){
            if (sign_b){
                dig_b[m-i-1] = b[i] - 48;
            }
            else{
                dig_b[m-i-1] = 57 - b[i];
            }
        }
        if (!sign_a){
            bool flg_a = true;
            sub[n-1] += 1;
            int pnt = 0;
            while (flg_a){
                if (dig_a[pnt] + 1 < 10){
                    flg_a = false;
                }
                dig_a[pnt] = (dig_a[pnt] + 1) % 10;
                pnt += 1;
            }
        }
        if (!sign_b){
            bool flg_b = true;
            sub[m-1] += 1;
            int pnt = 0;
            while (flg_b){
                if (dig_b[pnt] + 1 < 10){
                    flg_b = false;
                }
                dig_b[pnt] = (dig_b[pnt] + 1) % 10;
                pnt += 1;
            }
        }
        for (int i=0;i<lm+4;i++){
            ans[i] += dig_a[i] + dig_b[i];
            if (ans[i] >= 10){
                ans[i+1] += ans[i] / 10;
                ans[i] %= 10;
            }
        }
        bool total_sign = true;
        for (int i=lm+7;i>=0;i--){
            if (sub[i] != ans[i]){
                if (sub[i] > ans[i]){
                    total_sign = false;
                }
            }
        }
        string ans_s = "";
        bool chk_s = false;
        if (total_sign){
            for (int i=0;i<lm+7;i++){
                if (ans[i] >= sub[i]){
                    ans[i] -= sub[i];
                }
                else{
                    ans[i] += 10;
                    ans[i+1] -= 1;
                    ans[i] -= sub[i];
                }
            }
            for (int i=lm+7;i>=0;i--){
                if (ans[i] != 0){
                    chk_s = true;
                }
                if (chk_s){
                    ans_s = ans_s + ans[i].ToString();
                }
            }
        }
        else{
            ans_s = ans_s + "-";
            for (int i=0;i<lm+7;i++){
                if (sub[i] >= ans[i]){
                    sub[i] -= ans[i];
                }
                else{
                    sub[i] += 10;
                    sub[i+1] -= 1;
                    sub[i] -= ans[i];
                }
            }
            for (int i=lm+7;i>=0;i--){
                if (sub[i] != 0){
                    chk_s = true;
                }
                if (chk_s){
                    ans_s = ans_s + sub[i].ToString();
                }
            }
        }
        if (ans_s==""){
            return "0";
        }
        return ans_s;
    }
    public string Minus(string a, string b){
        int n = a.Length, m = b.Length;
        bool chk_a = check_num(a), chk_b = check_num(b);
        if (!chk_a || !chk_b){
            if (!chk_a && chk_b){
                Console.Error.WriteLine("Invalid input: 1st argument");
            }
            if (chk_a && !chk_b){
                Console.Error.WriteLine("Invalid input: 2nd argument");
            }
            if (!chk_a && !chk_b){
                Console.Error.WriteLine("Invalid inputs: 1st and 2nd arguments");
            }
        }
        int stb = 0;
        string alt_b = "";
        if (b[0] == '-'){
            stb = 1;
        }
        else{
            alt_b = "-";
        }
        for (int i=stb;i<m;i++){
            alt_b = alt_b + b[i];
        }
        return Plus(a, alt_b);
    }
    public string easy_Multiply(string a, string b){
        bool chk_a = check_num(a), chk_b = check_num(b);
        if (!chk_a || !chk_b){
            if (!chk_a && chk_b){
                Console.Error.WriteLine("Invalid input: 1st argument");
            }
            if (chk_a && !chk_b){
                Console.Error.WriteLine("Invalid input: 2nd argument");
            }
            if (!chk_a && !chk_b){
                Console.Error.WriteLine("Invalid inputs: 1st and 2nd arguments");
            }
        }
        string ans_s = "";
        int n = a.Length, m = b.Length;
        bool sign_a = (a[0] != '-'), sign_b = (b[0] != '-');
        int sta = 0, stb = 0;
        if (!sign_a){
            sta = 1;
        }
        if (!sign_b){
            stb = 1;
        }
        if ((sign_a && !sign_b) || (!sign_a && sign_b)){
            ans_s = ans_s + "-";
        }
        long[] ans_arr = new long[n+m+10];
        for (int i=0;i<n-sta;i++){
            for (int j=0;j<m-stb;j++){
                ans_arr[i+j] += (a[n-i-1] - 48) * (b[m-j-1] - 48);
            }
        }
        for (int i=0;i<n+m+8;i++){
            if (ans_arr[i] >= 10){
                ans_arr[i+1] += ans_arr[i] / 10;
                ans_arr[i] %= 10;
            }
        }
        bool chk_s = false;
        for (int i=n+m+8;i>=0;i--){
            if (ans_arr[i] != 0){
                chk_s = true;
            }
            if (chk_s){
                ans_s = ans_s + ans_arr[i].ToString();
            }
        }
        if (ans_s==""){
            return "0";
        }
        return ans_s;
    }
    public long simple_CRT(int n, long[] arr1, long[] arr2){
        long r = arr1[0];
        long bas = arr2[0];
        long x = 0, y = 0;
        long[] tmp;
        for (int i=1;i<n;i++){
            tmp = extgcd(bas, -arr2[i], arr1[i]-r);
            x = tmp[0];
            y = tmp[1];
            r += bas * x;
            bas *= arr2[i];
        }
        return safety_divisor(r, bas, true);
    }
    public string Multiply(string a, string b){
        bool chk_a = check_num(a), chk_b = check_num(b);
        if (!chk_a || !chk_b){
            if (!chk_a && chk_b){
                Console.Error.WriteLine("Invalid input: 1st argument");
            }
            if (chk_a && !chk_b){
                Console.Error.WriteLine("Invalid input: 2nd argument");
            }
            if (!chk_a && !chk_b){
                Console.Error.WriteLine("Invalid inputs: 1st and 2nd arguments");
            }
        }
        int n = a.Length, m = b.Length;
        string ans_s = "";
        bool sign_a = (a[0] != '-'), sign_b = (b[0] != '-');
        int sta = 0, stb = 0;
        if (!sign_a){
            sta = 1;
        }
        if (!sign_b){
            stb = 1;
        }
        if ((sign_a && !sign_b) || (!sign_a && sign_b)){
            ans_s = ans_s + "-";
        }
        int[] tmp_line = powlimit(n+m);
        int pl = tmp_line[0];
        int dpt = tmp_line[1];
        long[] a_arr = new long[pl];
        long[] b_arr = new long[pl];
        for (int i=sta;i<n;i++){
            a_arr[i-sta] = a[n-1+sta-i] - 48;
        }
        for (int i=stb;i<m;i++){
            b_arr[i-stb] = b[m-1+stb-i] - 48;
        }
        long[][] X = new long[this.cr][];
        long[][] Y = new long[this.cr][];
        for (int i=0;i<this.cr;i++){
            X[i] = NTT(a_arr, pl, i, dpt);
            Y[i] = NTT(b_arr, pl, i, dpt);
        }
        long[][] Z = new long[this.cr][];
        for (int i=0;i<this.cr;i++){
            Z[i] = new long[pl];
            for (int j=0;j<pl;j++){
                Z[i][j] = X[i][j]*Y[i][j]%modulo_list[i];
            }
            Z[i] = NTT(Z[i], pl, i, dpt, true);
        }
        long[] ans_l = new long[pl];
        long[] tmp = new long[this.cr];
        for (int i=0;i<pl;i++){
            for (int j=0;j<this.cr;j++){
                tmp[j] = Z[j][i];
            }
            ans_l[i] = simple_CRT(this.cr, tmp, modulo_list);
        }
        for (int i=0;i<pl;i++){
            if (ans_l[i] >= 10){
                ans_l[i+1] += ans_l[i] / 10;
                ans_l[i] %= 10;
            }
        }
        bool chk_s = false;
        for (int i=pl-1;i>=0;i--){
            if (ans_l[i] != 0){
                chk_s = true;
            }
            if (chk_s){
                ans_s = ans_s + ans_l[i].ToString();
            }
        }
        if (ans_s==""){
            return "0";
        }
        return ans_s;
    }
}