#define PROBLEM "https://judge.yosupo.jp/problem/multivariate_convolution_cyclic"
#pragma GCC optimize("O3")
#include<cassert>
#include<cstdint>
#include<vector>#include"../../math/modint.hpp"
#include"../../math/multivariate_convolution.hpp"
#include"../../utilities/fast_io.hpp"namespace{usingmint=m1une::math::DynamicModInt<0>;template<classMint>std::vector<Mint>naive(conststd::vector<int>&dimensions,conststd::vector<Mint>&first,conststd::vector<Mint>&second){constintsize=int(first.size());std::vector<Mint>result(size);for(intleft=0;left<size;left++){for(intright=0;right<size;right++){intleft_index=left;intright_index=right;inttarget=0;intstride=1;for(intdimension:dimensions){constintcoordinate=(left_index%dimension+right_index%dimension)%dimension;target+=stride*coordinate;stride*=dimension;left_index/=dimension;right_index/=dimension;}result[target]+=first[left]*second[right];}}returnresult;}template<classMint>voidtest_fixed_mod_randomized(uint64_tseed){uint64_tstate=seed;autorandom=[&state](){state^=state<<7;state^=state>>9;returnstate;};constintdimensions_to_test[]={1,2,3,4,5,7,8};for(inttrial=0;trial<120;trial++){constintvariable_count=int(random()%5);std::vector<int>dimensions(variable_count);intsize=1;for(int&dimension:dimensions){dimension=dimensions_to_test[random()%7];size*=dimension;}if(size>140){trial--;continue;}std::vector<Mint>first(size),second(size);for(Mint&value:first)value=random()%Mint::mod();for(Mint&value:second)value=random()%Mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}}voidtest_randomized(){mint::set_mod(97);uint64_tstate=0xfedcba987654321ULL;autorandom=[&state](){state^=state<<7;state^=state>>9;returnstate;};constintdimensions_to_test[]={1,2,3,4,5,6,7,8};for(inttrial=0;trial<300;trial++){constintvariable_count=int(random()%4);std::vector<int>dimensions(variable_count);intsize=1;for(int&dimension:dimensions){dimension=dimensions_to_test[random()%8];size*=dimension;}if(size>200){trial--;continue;}std::vector<mint>first(size),second(size);for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}std::vector<int>dimensions={96};std::vector<mint>first(96),second(96);for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));dimensions={1,5,1,7};first.assign(35,mint(0));second.assign(35,mint(0));for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}voidtest_nested_vectors(){mint::set_mod(97);std::vector<std::vector<mint>>first(3,std::vector<mint>(2));std::vector<std::vector<mint>>second(3,std::vector<mint>(2));intvalue=1;for(auto&row:first){for(mint&coefficient:row)coefficient=value++;}value=7;for(auto&row:second){for(mint&coefficient:row)coefficient=value++;}std::vector<mint>flattened_first,flattened_second;for(constauto&row:first){flattened_first.insert(flattened_first.end(),row.begin(),row.end());}for(constauto&row:second){flattened_second.insert(flattened_second.end(),row.begin(),row.end());}std::vector<mint>expected=naive(std::vector<int>{2,3},flattened_first,flattened_second);constautoresult=m1une::math::multivariate_convolution_cyclic(first,second);intindex=0;for(constauto&row:result){for(mintcoefficient:row)assert(coefficient==expected[index++]);}// Dimension 5 does not divide 97 - 1, so this exercises the mixed-radix// fallback through the nested-vector overload.first.assign(5,std::vector<mint>(3));second.assign(5,std::vector<mint>(3));for(auto&row:first){for(mint&coefficient:row)coefficient=value++;}for(auto&row:second){for(mint&coefficient:row)coefficient=value++;}flattened_first.clear();flattened_second.clear();for(constauto&row:first){flattened_first.insert(flattened_first.end(),row.begin(),row.end());}for(constauto&row:second){flattened_second.insert(flattened_second.end(),row.begin(),row.end());}expected=naive(std::vector<int>{3,5},flattened_first,flattened_second);constautofallback_result=m1une::math::multivariate_convolution_cyclic(first,second);index=0;for(constauto&row:fallback_result){for(mintcoefficient:row)assert(coefficient==expected[index++]);}}}// namespaceintmain(){test_randomized();test_nested_vectors();test_fixed_mod_randomized<m1une::math::modint998244353>(0x123456789abcdefULL);test_fixed_mod_randomized<m1une::math::modint1000000007>(0x314159265358979ULL);m1une::utilities::FastInputinput;m1une::utilities::FastOutputoutput;uint32_tmodulus=1;intvariable_count=0;input.read(modulus,variable_count);mint::set_mod(modulus);std::vector<int>dimensions(variable_count);input.read(dimensions);intsize=1;for(intdimension:dimensions)size*=dimension;std::vector<mint>first(size),second(size);input.read(first);input.read(second);output.println(m1une::math::multivariate_convolution_cyclic(dimensions,first,second));}
#line 1 "verify/math/multivariate_convolution_cyclic.test.cpp"
#define PROBLEM "https://judge.yosupo.jp/problem/multivariate_convolution_cyclic"
#pragma GCC optimize("O3")
#include<cassert>
#include<cstdint>
#include<vector>#line 1 "math/modint.hpp"
#line 6 "math/modint.hpp"
#include<iostream>
#include<type_traits>
#include<utility>namespacem1une{namespacemath{template<uint32_tModulus>structModInt{static_assert(0<Modulus,"Modulus must be positive");private:uint32_t_v;public:staticconstexpruint32_tmod(){returnModulus;}staticconstexprModIntraw(uint32_tv)noexcept{ModIntx;x._v=v;returnx;}constexprModInt()noexcept:_v(0){}template<classInteger,std::enable_if_t<std::is_integral_v<Integer>,int>=0>constexprModInt(Integerv)noexcept{ifconstexpr(std::is_signed_v<Integer>){int64_tx=static_cast<int64_t>(v)%static_cast<int64_t>(Modulus);if(x<0)x+=Modulus;_v=static_cast<uint32_t>(x);}else{_v=static_cast<uint32_t>(static_cast<uint64_t>(v)%Modulus);}}constexpruint32_tval()constnoexcept{return_v;}constexprModInt&operator++()noexcept{_v++;if(_v==Modulus)_v=0;return*this;}constexprModInt&operator--()noexcept{if(_v==0)_v=Modulus;_v--;return*this;}constexprModIntoperator++(int)noexcept{ModIntres=*this;++*this;returnres;}constexprModIntoperator--(int)noexcept{ModIntres=*this;--*this;returnres;}constexprModInt&operator+=(constModInt&rhs)noexcept{_v+=rhs._v;if(_v>=Modulus)_v-=Modulus;return*this;}constexprModInt&operator-=(constModInt&rhs)noexcept{_v-=rhs._v;if(_v>=Modulus)_v+=Modulus;return*this;}constexprModInt&operator*=(constModInt&rhs)noexcept{uint64_tz=_v;z*=rhs._v;_v=static_cast<uint32_t>(z%Modulus);return*this;}constexprModInt&operator/=(constModInt&rhs)noexcept{return*this*=rhs.inv();}constexprModIntoperator+(constModInt&rhs)constnoexcept{returnModInt(*this)+=rhs;}constexprModIntoperator-(constModInt&rhs)constnoexcept{returnModInt(*this)-=rhs;}constexprModIntoperator*(constModInt&rhs)constnoexcept{returnModInt(*this)*=rhs;}constexprModIntoperator/(constModInt&rhs)constnoexcept{returnModInt(*this)/=rhs;}constexprbooloperator==(constModInt&rhs)constnoexcept{return_v==rhs._v;}constexprbooloperator!=(constModInt&rhs)constnoexcept{return_v!=rhs._v;}constexprModIntpow(longlongn)constnoexcept{ModIntres=raw(1%Modulus);ModIntx=n<0?inv():*this;uint64_texponent=n<0?uint64_t(-(n+1))+1:uint64_t(n);while(exponent>0){if(exponent&1)res*=x;x*=x;exponent>>=1;}returnres;}constexprModIntinv()constnoexcept{int64_ta=_v,b=Modulus,u=1,v=0;while(b){int64_tt=a/b;a-=t*b;std::swap(a,b);u-=t*v;std::swap(u,v);}assert(a==1);u%=Modulus;if(u<0)u+=Modulus;returnraw(static_cast<uint32_t>(u));}friendstd::ostream&operator<<(std::ostream&os,constModInt&rhs){returnos<<rhs._v;}friendstd::istream&operator>>(std::istream&is,ModInt&rhs){longlongv;is>>v;rhs=ModInt(v);returnis;}};usingmodint998244353=ModInt<998244353>;usingmodint1000000007=ModInt<1000000007>;template<intId=0>structDynamicModInt{private:uint32_t_v;inlinestaticuint32_t_mod=1;public:staticuint32_tmod()noexcept{return_mod;}staticvoidset_mod(uint32_tmodulus)noexcept{assert(modulus>0);assert(modulus<=uint32_t(1)<<31);_mod=modulus;}staticDynamicModIntraw(uint32_tv)noexcept{assert(v<_mod);DynamicModIntx;x._v=v;returnx;}DynamicModInt()noexcept:_v(0){}template<classInteger,std::enable_if_t<std::is_integral_v<Integer>,int>=0>DynamicModInt(Integerv)noexcept{ifconstexpr(std::is_signed_v<Integer>){int64_tx=static_cast<int64_t>(v)%static_cast<int64_t>(_mod);if(x<0)x+=_mod;_v=static_cast<uint32_t>(x);}else{_v=static_cast<uint32_t>(static_cast<uint64_t>(v)%_mod);}}uint32_tval()constnoexcept{return_v;}DynamicModInt&operator++()noexcept{_v++;if(_v==_mod)_v=0;return*this;}DynamicModInt&operator--()noexcept{if(_v==0)_v=_mod;_v--;return*this;}DynamicModIntoperator++(int)noexcept{DynamicModIntresult=*this;++*this;returnresult;}DynamicModIntoperator--(int)noexcept{DynamicModIntresult=*this;--*this;returnresult;}DynamicModInt&operator+=(constDynamicModInt&rhs)noexcept{_v+=rhs._v;if(_v>=_mod)_v-=_mod;return*this;}DynamicModInt&operator-=(constDynamicModInt&rhs)noexcept{_v-=rhs._v;if(_v>=_mod)_v+=_mod;return*this;}DynamicModInt&operator*=(constDynamicModInt&rhs)noexcept{_v=static_cast<uint32_t>(uint64_t(_v)*rhs._v%_mod);return*this;}DynamicModInt&operator/=(constDynamicModInt&rhs)noexcept{return*this*=rhs.inv();}DynamicModIntoperator+(constDynamicModInt&rhs)constnoexcept{returnDynamicModInt(*this)+=rhs;}DynamicModIntoperator-(constDynamicModInt&rhs)constnoexcept{returnDynamicModInt(*this)-=rhs;}DynamicModIntoperator*(constDynamicModInt&rhs)constnoexcept{returnDynamicModInt(*this)*=rhs;}DynamicModIntoperator/(constDynamicModInt&rhs)constnoexcept{returnDynamicModInt(*this)/=rhs;}booloperator==(constDynamicModInt&rhs)constnoexcept{return_v==rhs._v;}booloperator!=(constDynamicModInt&rhs)constnoexcept{return_v!=rhs._v;}DynamicModIntpow(longlongexponent)constnoexcept{DynamicModIntresult=raw(1%_mod);DynamicModIntbase=exponent<0?inv():*this;uint64_tmagnitude=exponent<0?uint64_t(-(exponent+1))+1:uint64_t(exponent);while(magnitude>0){if(magnitude&1)result*=base;base*=base;magnitude>>=1;}returnresult;}DynamicModIntinv()constnoexcept{int64_ta=_v,b=_mod,u=1,v=0;while(b){int64_tquotient=a/b;a-=quotient*b;std::swap(a,b);u-=quotient*v;std::swap(u,v);}assert(a==1);u%=_mod;if(u<0)u+=_mod;returnraw(static_cast<uint32_t>(u));}friendstd::ostream&operator<<(std::ostream&os,constDynamicModInt&rhs){returnos<<rhs._v;}friendstd::istream&operator>>(std::istream&is,DynamicModInt&rhs){longlongvalue;is>>value;rhs=DynamicModInt(value);returnis;}};}// namespace math}// namespace m1une#line 1 "math/multivariate_convolution.hpp"
#include<algorithm>
#line 7 "math/multivariate_convolution.hpp"
#include<limits>
#line 11 "math/multivariate_convolution.hpp"
#line 1 "math/fps/convolution.hpp"
#line 5 "math/fps/convolution.hpp"
#include<array>
#line 8 "math/fps/convolution.hpp"
#include<cstring>
#include<new>
#line 13 "math/fps/convolution.hpp"
#if defined(__GNUC__) && !defined(__clang__) && \
(defined(__x86_64__) || defined(__i386__)) && \
!defined(M1UNE_FPS_DISABLE_X86_SIMD)
#include<immintrin.h>
#define M1UNE_FPS_HAS_X86_SIMD 1
#pragma GCC push_options
#pragma GCC target("avx2,bmi")
#endif
#line 1 "math/fps/internal/ntt998_faster.hpp"
#ifdef M1UNE_FPS_HAS_X86_SIMD
#line 9 "math/fps/internal/ntt998_faster.hpp"
#include<immintrin.h>namespacem1une{namespacefps{namespaceinternal{namespacefast998_v2{// Fixed-modulus AVX2 transform with an in-register degree-8 residue product.usingu32=unsigned;usingu64=unsignedlonglong;usingidt=std::size_t;usingI256=__m256i;inlinevoidstore256(void*p,I256x){_mm256_store_si256((I256*)p,x);}inlineI256load256(constvoid*p){return_mm256_load_si256((constI256*)p);}constexpru32shrk(u32x,u32M){returnstd::min(x,x-M);}constexpru32dilt(u32x,u32M){returnstd::min(x,x+M);}constexpru32reduce(u64x,u32niv,u32M){return(x+u64(u32(x)*niv)*M)>>32;}constexpru32mul(u32x,u32y,u32niv,u32M){returnreduce(u64(x)*y,niv,M);}constexpru32mul_s(u32x,u32y,u32niv,u32M){returnshrk(reduce(u64(x)*y,niv,M),M);}constexpru32qpw(u32a,u32b,u32niv,u32M,u32r){for(;b;b>>=1,a=mul(a,a,niv,M)){if(b&1){r=mul(r,a,niv,M);}}returnr;}constexpru32qpw_s(u32a,u32b,u32niv,u32M,u32r){returnshrk(qpw(a,b,niv,M,r),M);}inlineI256shrk32(I256x,I256M){return_mm256_min_epu32(x,_mm256_sub_epi32(x,M));}inlineI256dilt32(I256x,I256M){return_mm256_min_epu32(x,_mm256_add_epi32(x,M));}inlineI256Ladd32(I256x,I256y,I256){return_mm256_add_epi32(x,y);}inlineI256Lsub32(I256x,I256y,I256M){return_mm256_add_epi32(_mm256_sub_epi32(x,y),M);}inlineI256add32(I256x,I256y,I256M){returnshrk32(_mm256_add_epi32(x,y),M);}inlineI256sub32(I256x,I256y,I256M){returndilt32(_mm256_sub_epi32(x,y),M);}template<intmsk>inlineI256neg32_m(I256x,I256M){return_mm256_blend_epi32(x,_mm256_sub_epi32(M,x),msk);}inlineI256reduce(I256a,I256b,I256niv,I256M){I256c=_mm256_mul_epu32(a,niv),d=_mm256_mul_epu32(b,niv);c=_mm256_mul_epu32(c,M),d=_mm256_mul_epu32(d,M);return_mm256_blend_epi32(_mm256_srli_epi64(_mm256_add_epi64(a,c),32),_mm256_add_epi64(b,d),0xaa);}inlineI256mul(I256a,I256b,I256niv,I256M){returnreduce(_mm256_mul_epu32(a,b),_mm256_mul_epu32(_mm256_srli_epi64(a,32),_mm256_srli_epi64(b,32)),niv,M);}inlineI256mul_s(I256a,I256b,I256niv,I256M){returnshrk32(mul(a,b,niv,M),M);}inlineI256mul_bsm(I256a,I256b,I256niv,I256M){returnreduce(_mm256_mul_epu32(a,b),_mm256_mul_epu32(_mm256_srli_epi64(a,32),b),niv,M);}inlineI256mul_bsmfxd(I256a,I256b,I256bniv,I256M){I256cc=_mm256_mul_epu32(a,bniv),dd=_mm256_mul_epu32(_mm256_srli_epi64(a,32),bniv);I256c=_mm256_mul_epu32(a,b),d=_mm256_mul_epu32(_mm256_srli_epi64(a,32),b);cc=_mm256_mul_epu32(cc,M),dd=_mm256_mul_epu32(dd,M);return_mm256_blend_epi32(_mm256_srli_epi64(_mm256_add_epi64(c,cc),32),_mm256_add_epi64(d,dd),0xaa);}inlineI256mul_bfxd(I256a,I256b,I256bniv,I256M){I256cc=_mm256_mul_epu32(a,bniv),dd=_mm256_mul_epu32(_mm256_srli_epi64(a,32),_mm256_srli_epi64(bniv,32));I256c=_mm256_mul_epu32(a,b),d=_mm256_mul_epu32(_mm256_srli_epi64(a,32),_mm256_srli_epi64(b,32));cc=_mm256_mul_epu32(cc,M),dd=_mm256_mul_epu32(dd,M);return_mm256_blend_epi32(_mm256_srli_epi64(_mm256_add_epi64(c,cc),32),_mm256_add_epi64(d,dd),0xaa);}inlineI256mul_upd_rt(I256a,I256bu,I256M){I256cc=_mm256_mul_epu32(a,bu),c=_mm256_mul_epu32(a,_mm256_srli_epi64(bu,32));cc=_mm256_mul_epu32(cc,M);returnshrk32(_mm256_srli_epi64(_mm256_add_epi64(c,cc),32),M);}constexprauto_mxlg=26,_lg_itth=6;constexprauto_itth=idt(1)<<_lg_itth;static_assert(_lg_itth%2==0);structFNTT32_info{u32mod,mod2,niv,one,r2,r3,img,imgniv,RT1[_mxlg];alignas(32)std::array<u32,8>rt3[_mxlg-2],rt3i[_mxlg-2],bwbr,bwb,bwbi,rt4[_mxlg-3],rt4niv[_mxlg-3],rt4i[_mxlg-3],rt4iniv[_mxlg-3],pr2,pr4,pr2niv,pr4niv,pr2i,pr2iniv,pr4i,pr4iniv;constexprFNTT32_info(constu32m):mod(m),mod2(m*2),niv([&]{u32n=2+m;for(inti=0;i<4;++i){n*=2+m*n;}returnn;}()),one((-m)%m),r2((-u64(m))%m),r3(mul_s(r2,r2,niv,m)),img{},imgniv{},RT1{},rt3{},rt3i{},bwbr{},bwb{},bwbi{},rt4{},rt4niv{},rt4i{},rt4iniv{},pr2{},pr4{},pr2niv{},pr4niv{},pr2i{},pr2iniv{},pr4i{},pr4iniv{}{constintk=__builtin_ctz(m-1);u32_g=mul(3,r2,niv,mod);for(;;++_g){if(qpw_s(_g,mod>>1,niv,mod,one)!=one){break;}}_g=qpw(_g,mod>>k,niv,mod,one);u32rt1[_mxlg-1],rt1i[_mxlg-1];rt1[k-2]=_g,rt1i[k-2]=qpw(_g,mod-2,niv,mod,one);for(inti=k-2;i>0;--i){rt1[i-1]=mul(rt1[i],rt1[i],niv,mod);rt1i[i-1]=mul(rt1i[i],rt1i[i],niv,mod);}RT1[k-1]=qpw_s(_g,3,niv,mod,one);for(inti=k-1;i>0;--i){RT1[i-1]=mul_s(RT1[i],RT1[i],niv,mod);}img=rt1[0],imgniv=img*niv;bwbr={one,0,one,0,one};bwb={rt1[1],0,rt1[0],0,mod-mul_s(rt1[0],rt1[1],niv,mod)};bwbi={rt1i[1],0,rt1i[0],0,mul_s(rt1i[0],rt1i[1],niv,mod)};u32pr=one,pri=one;for(inti=0;i<k-2;++i){constu32r=mul_s(pr,rt1[i+1],niv,mod),ri=mul_s(pri,rt1i[i+1],niv,mod);constu32r2=mul_s(r,r,niv,mod),r2i=mul_s(ri,ri,niv,mod);constu32r3=mul_s(r,r2,niv,mod),r3i=mul_s(ri,r2i,niv,mod);rt3[i]={r*niv,r,r2*niv,r2,r3*niv,r3};rt3i[i]={ri*niv,ri,r2i*niv,r2i,r3i*niv,r3i};pr=mul(pr,rt1i[i+1],niv,mod),pri=mul(pri,rt1[i+1],niv,mod);}pr=one,pri=one;for(inti=0;i<k-3;++i){constu32r=mul_s(pr,rt1[i+2],niv,mod),ri=mul_s(pri,rt1i[i+2],niv,mod);rt4[i][0]=rt4i[i][0]=one;for(intj=1;j<8;++j){rt4[i][j]=mul_s(rt4[i][j-1],r,niv,mod);rt4i[i][j]=mul_s(rt4i[i][j-1],ri,niv,mod);}for(intj=0;j<8;++j){rt4niv[i][j]=rt4[i][j]*niv;rt4iniv[i][j]=rt4i[i][j]*niv;}pr=mul(pr,rt1i[i+2],niv,mod),pri=mul(pri,rt1[i+2],niv,mod);}pr2={one,one,one,img,one,one,one,img};pr4={one,one,one,one,one,rt1[1],img,mul_s(img,rt1[1],niv,mod)};constu32nr2=mod-r2,imgr2=mul_s(img,r2,niv,mod);pr2i={nr2,nr2,nr2,imgr2,nr2,nr2,nr2,imgr2};pr4i={one,one,one,one,one,rt1i[1],rt1i[0],mul_s(rt1i[0],rt1i[1],niv,mod)};for(intj=0;j<8;++j){pr2niv[j]=pr2[j]*niv,pr4niv[j]=pr4[j]*niv;pr2iniv[j]=pr2i[j]*niv,pr4iniv[j]=pr4i[j]*niv;}}};inlinevoidvector_dif(I256*constf,constidtn,constFNTT32_info*info){alignas(32)std::array<u32,8>st_1[_mxlg>>1];constI256Mod=_mm256_set1_epi32(info->mod),Mod2=_mm256_set1_epi32(info->mod2),Niv=_mm256_set1_epi32(info->niv);constI256Img=_mm256_set1_epi32(info->img),ImgNiv=_mm256_set1_epi32(info->imgniv),id=_mm256_setr_epi32(0,2,0,4,0,2,0,4);constintlgn=__builtin_ctzll(n);std::fill(st_1,st_1+(lgn>>1),info->bwb);constidtnn=n>>(lgn&1),m=std::min(n,_itth),mm=std::min(nn,_itth);// I256 rr=_mm256_set1_epi32(info->one);if(nn!=n){for(idti=0;i<nn;++i){autoconstp0=f+i,p1=f+nn+i;constautof0=load256(p0),f1=load256(p1);constautog0=add32(f0,f1,Mod2),g1=Lsub32(f0,f1,Mod2);store256(p0,g0),store256(p1,g1);}}for(idtL=nn>>2;L>0;L>>=2){for(idti=0;i<L;++i){autoconstp0=f+i,p1=p0+L,p2=p1+L,p3=p2+L;constautof1=load256(p1),f3=load256(p3),f2=load256(p2),f0=load256(p0);constautog3=mul_bsmfxd(Lsub32(f1,f3,Mod2),Img,ImgNiv,Mod),g1=add32(f1,f3,Mod2);constautog0=add32(f0,f2,Mod2),g2=sub32(f0,f2,Mod2);constautoh0=add32(g0,g1,Mod2),h1=Lsub32(g0,g1,Mod2);constautoh2=Ladd32(g2,g3,Mod2),h3=Lsub32(g2,g3,Mod2);store256(p0,h0),store256(p1,h1),store256(p2,h2),store256(p3,h3);}}for(idtj=0;j<n;j+=m){intt=((j==0)?std::min(_lg_itth,lgn):__builtin_ctzll(j))&-2,p=(t-2)>>1;for(idtL=(idt(1)<<t)>>2;L>=_itth;L>>=2,t-=2,--p){autort=load256(st_1+p);constautor1=_mm256_permutevar8x32_epi32(rt,id);constautor1Niv=_mm256_permutevar8x32_epi32(_mm256_mul_epu32(rt,Niv),id);rt=mul_upd_rt(rt,load256(info->rt3+__builtin_ctzll(~j>>t)),Mod);constautor2=_mm256_shuffle_epi32(r1,_MM_PERM_BBBB),nr3=_mm256_shuffle_epi32(r1,_MM_PERM_DDDD);constautor2Niv=_mm256_shuffle_epi32(r1Niv,_MM_PERM_BBBB),nr3Niv=_mm256_shuffle_epi32(r1Niv,_MM_PERM_DDDD);store256(st_1+p,rt);for(idti=0;i<L;++i){autoconstp0=f+i+j,p1=p0+L,p2=p1+L,p3=p2+L;constautof1=load256(p1),f3=load256(p3),f2=load256(p2),f0=load256(p0);constautog1=mul_bsmfxd(f1,r1,r1Niv,Mod),ng3=mul_bsmfxd(f3,nr3,nr3Niv,Mod);constautog2=mul_bsmfxd(f2,r2,r2Niv,Mod),g0=shrk32(f0,Mod2);constautoh3=mul_bsmfxd(Ladd32(g1,ng3,Mod2),Img,ImgNiv,Mod),h1=sub32(g1,ng3,Mod2);constautoh0=add32(g0,g2,Mod2),h2=sub32(g0,g2,Mod2);constautou0=Ladd32(h0,h1,Mod2),u1=Lsub32(h0,h1,Mod2);constautou2=Ladd32(h2,h3,Mod2),u3=Lsub32(h2,h3,Mod2);store256(p0,u0),store256(p1,u1),store256(p2,u2),store256(p3,u3);}}I256*constg=f+j;for(idtl=mm,L=mm>>2;L;l=L,L>>=2,t-=2,--p){autort=load256(st_1+p);for(idti=(j==0?l:0),k=(j+i)>>t;i<m;i+=l,++k){constautor1=_mm256_permutevar8x32_epi32(rt,id);constautor2=_mm256_shuffle_epi32(r1,_MM_PERM_BBBB);constautonr3=_mm256_shuffle_epi32(r1,_MM_PERM_DDDD);for(idtj=0;j<L;++j){autoconstp0=g+i+j,p1=p0+L,p2=p1+L,p3=p2+L;constautof1=load256(p1),f3=load256(p3),f2=load256(p2),f0=load256(p0);constautog1=mul_bsm(f1,r1,Niv,Mod),ng3=mul_bsm(f3,nr3,Niv,Mod);constautog2=mul_bsm(f2,r2,Niv,Mod),g0=shrk32(f0,Mod2);constautoh3=mul_bsmfxd(Ladd32(g1,ng3,Mod2),Img,ImgNiv,Mod),h1=sub32(g1,ng3,Mod2);constautoh0=add32(g0,g2,Mod2),h2=sub32(g0,g2,Mod2);constautou0=Ladd32(h0,h1,Mod2),u1=Lsub32(h0,h1,Mod2);constautou2=Ladd32(h2,h3,Mod2),u3=Lsub32(h2,h3,Mod2);store256(p0,u0),store256(p1,u1),store256(p2,u2),store256(p3,u3);}rt=mul_upd_rt(rt,load256(info->rt3+__builtin_ctzll(~k)),Mod);}store256(st_1+p,rt);}// const auto pr2=load256(&info->pr2),pr4=load256(&info->pr4);// const auto pr2Niv=load256(&info->pr2niv),pr4Niv=load256(&info->pr4niv);// for(idt i=j;i<j+m;++i){// auto fi=load256(f+i);// fi=mul(fi,rr,Niv,Mod);// rr=shrk32(mul_bfxd(rr,load256(info->rt4+__builtin_ctzll(~i)),load256(info->rt4niv+__builtin_ctzll(~i)),Mod),Mod);// fi=mul_bfxd(Ladd32(neg32_m<0xf0>(fi,Mod2),_mm256_permute2x128_si256(fi,fi,1),Mod2),pr4,pr4Niv,Mod);// fi=mul_bfxd(Ladd32(neg32_m<0xcc>(fi,Mod2),_mm256_shuffle_epi32(fi,0x4e),Mod2),pr2,pr2Niv,Mod);// fi=sub32(_mm256_shuffle_epi32(fi,0xb1),neg32_m<0x55>(fi,Mod2),Mod2);// store256(f+i,fi);// }}}template<boolshrk=false>inlinevoidvector_dit(I256*constf,idtn,constFNTT32_info*constinfo){alignas(32)std::array<u32,8>st_1[_mxlg>>1];constI256Mod=_mm256_set1_epi32(info->mod),Mod2=_mm256_set1_epi32(info->mod2),Niv=_mm256_set1_epi32(info->niv);constI256Img=_mm256_set1_epi32(info->img),ImgNiv=_mm256_set1_epi32(info->imgniv),id=_mm256_setr_epi32(0,2,0,4,0,2,0,4);constintlgn=__builtin_ctzll(n);std::fill(st_1,st_1+(_lg_itth>>1),info->bwbr);std::fill(st_1+(_lg_itth>>1),st_1+(_mxlg>>1),info->bwbi);constidtnn=n>>(lgn&1),mm=std::min(nn,_itth);// I256 rr=_mm256_set1_epi32((info->mod-1)>>(lgn+3));for(idtj=0;j<n;j+=mm){// const auto pr2=load256(&info->pr2i),pr4=load256(&info->pr4i);// const auto pr2Niv=load256(&info->pr2iniv),pr4Niv=load256(&info->pr4iniv);// for(idt i=j;i<j+mm;++i){// auto fi=load256(f+i);// const auto rt=rr;// rr=shrk32(mul_bfxd(rr,load256(info->rt4i+__builtin_ctzll(~i)),load256(info->rt4iniv+__builtin_ctzll(~i)),Mod),Mod);// fi=mul_bfxd(Ladd32(neg32_m<0xaa>(fi,Mod2),_mm256_shuffle_epi32(fi,0xb1),Mod2),pr2,pr2Niv,Mod);// fi=mul_bfxd(Ladd32(neg32_m<0xcc>(fi,Mod2),_mm256_shuffle_epi32(fi,0x4e),Mod2),pr4,pr4Niv,Mod);// fi=mul(Ladd32(neg32_m<0xf0>(fi,Mod2),_mm256_permute2x128_si256(fi,fi,1),Mod2),rt,Niv,Mod);// store256(f+i,fi);// }I256*constg=f+j;intt=2,p=0;for(idtl=4,L=1;l<=mm;L=l,l<<=2,t+=2,++p){autort=load256(st_1+p);for(idti=0,k=j>>t;i<mm;i+=l,++k){constautor1=_mm256_permutevar8x32_epi32(rt,id);constautor2=_mm256_shuffle_epi32(r1,_MM_PERM_BBBB);constautor3=_mm256_shuffle_epi32(r1,_MM_PERM_DDDD);for(idtj=0;j<L;++j){autoconstp0=g+i+j,p1=p0+L,p2=p1+L,p3=p2+L;constautof0=load256(p0),f1=load256(p1),f2=load256(p2),f3=load256(p3);constautog0=add32(f0,f1,Mod2),g1=sub32(f0,f1,Mod2);constautog2=add32(f2,f3,Mod2),g3=mul_bsmfxd(Lsub32(f3,f2,Mod2),Img,ImgNiv,Mod);constautoh0=Ladd32(g0,g2,Mod2),h1=Ladd32(g1,g3,Mod2);constautoh2=Lsub32(g0,g2,Mod2),h3=Lsub32(g1,g3,Mod2);constautou0=shrk32(h0,Mod2),u1=mul_bsm(h1,r1,Niv,Mod);constautou2=mul_bsm(h2,r2,Niv,Mod),u3=mul_bsm(h3,r3,Niv,Mod);store256(p0,u0),store256(p1,u1),store256(p2,u2),store256(p3,u3);}rt=mul_upd_rt(rt,load256(info->rt3i+__builtin_ctzll(~k)),Mod);}store256(st_1+p,rt);}inttt=std::min(__builtin_ctzll(~(j>>_lg_itth))+_lg_itth,lgn);for(idtL=_itth,l=L<<2;t<=tt;L=l,l<<=2,t+=2,++p){if((j+_itth)==l){if(shrk&&l==n){for(idti=0;i<L;++i){autoconstp0=f+i,p1=p0+L,p2=p1+L,p3=p2+L;constautof2=load256(p2),f3=load256(p3),f0=load256(p0),f1=load256(p1);constautog3=mul_bsmfxd(Lsub32(f3,f2,Mod2),Img,ImgNiv,Mod),g2=add32(f2,f3,Mod2);constautog0=add32(f0,f1,Mod2),g1=sub32(f0,f1,Mod2);constautoh0=add32(g0,g2,Mod2),h1=add32(g1,g3,Mod2);constautoh2=sub32(g0,g2,Mod2),h3=sub32(g1,g3,Mod2);constautou0=shrk32(h0,Mod),u1=shrk32(h1,Mod);constautou2=shrk32(h2,Mod),u3=shrk32(h3,Mod);store256(p0,u0),store256(p1,u1),store256(p2,u2),store256(p3,u3);}}else{for(idti=0;i<L;++i){autoconstp0=f+i,p1=p0+L,p2=p1+L,p3=p2+L;constautof2=load256(p2),f3=load256(p3),f0=load256(p0),f1=load256(p1);constautog3=mul_bsmfxd(Lsub32(f3,f2,Mod2),Img,ImgNiv,Mod),g2=add32(f2,f3,Mod2);constautog0=add32(f0,f1,Mod2),g1=sub32(f0,f1,Mod2);constautoh0=add32(g0,g2,Mod2),h1=add32(g1,g3,Mod2);constautoh2=sub32(g0,g2,Mod2),h3=sub32(g1,g3,Mod2);store256(p0,h0),store256(p1,h1),store256(p2,h2),store256(p3,h3);}}}else{autort=load256(st_1+p);constautor1=_mm256_permutevar8x32_epi32(rt,id);constautor1Niv=_mm256_permutevar8x32_epi32(_mm256_mul_epu32(rt,Niv),id);rt=mul_upd_rt(rt,load256(info->rt3i+__builtin_ctzll(~j>>t)),Mod);constautor2=_mm256_shuffle_epi32(r1,_MM_PERM_BBBB),r3=_mm256_shuffle_epi32(r1,_MM_PERM_DDDD);constautor2Niv=_mm256_shuffle_epi32(r1Niv,_MM_PERM_BBBB),r3Niv=_mm256_shuffle_epi32(r1Niv,_MM_PERM_DDDD);store256(st_1+p,rt);for(idti=0;i<L;++i){autoconstp0=f+j+_itth-l+i,p1=p0+L,p2=p1+L,p3=p2+L;constautof0=load256(p0),f1=load256(p1),f2=load256(p2),f3=load256(p3);constautog0=add32(f0,f1,Mod2),g1=sub32(f0,f1,Mod2);constautog2=add32(f2,f3,Mod2),g3=mul_bsmfxd(Lsub32(f3,f2,Mod2),Img,ImgNiv,Mod);constautoh0=Ladd32(g0,g2,Mod2),h1=Ladd32(g1,g3,Mod2);constautoh2=Lsub32(g0,g2,Mod2),h3=Lsub32(g1,g3,Mod2);constautou0=shrk32(h0,Mod2),u1=mul_bsmfxd(h1,r1,r1Niv,Mod);constautou2=mul_bsmfxd(h2,r2,r2Niv,Mod),u3=mul_bsmfxd(h3,r3,r3Niv,Mod);store256(p0,u0),store256(p1,u1),store256(p2,u2),store256(p3,u3);}}}}if(shrk&&nn==n&&n<=_itth){for(idti=0;i<n;++i){constautof0=load256(f+i);store256(f+i,shrk32(f0,Mod));}}if(nn!=n){for(idti=0;i<nn;++i){autoconstp0=f+i,p1=f+nn+i;constautof0=load256(p0),f1=load256(p1);constautog0=add32(f0,f1,Mod2),g1=sub32(f0,f1,Mod2);ifconstexpr(shrk){constautoh0=shrk32(g0,Mod),h1=shrk32(g1,Mod);store256(p0,h0),store256(p1,h1);}else{store256(p0,g0),store256(p1,g1);}}}}// Returns fx * f[0,8) * g[0,8) (mod x^8 - ww).[[gnu::always_inline]]inlineI256convolve8(constI256*f,constI256*g,I256ww,I256fx,I256Niv,I256Mod,I256Mod2){constautoraa=load256(f),rbb=load256(g);constautotaa=shrk32(raa,Mod2),bb=shrk32(mul_bsm(rbb,fx,Niv,Mod),Mod);constautoaw=shrk32(mul_bsm(taa,ww,Niv,Mod),Mod);constautoaa=shrk32(taa,Mod);constautoawa=_mm256_permute2x128_si256(aa,aw,3);constautob0=_mm256_permute4x64_epi64(bb,0x00),b1=_mm256_shuffle_epi32(b0,_MM_PERM_CDAB);constautoa0=aa,a1=_mm256_srli_epi64(a0,32);constautoaw7=_mm256_alignr_epi8(aa,awa,12);autores00=_mm256_mul_epu32(a0,b0);autores01=_mm256_mul_epu32(a1,b0);autores10=_mm256_mul_epu32(aw7,b1);autores11=_mm256_mul_epu32(a0,b1);constautob2=_mm256_permute4x64_epi64(bb,0x55),b3=_mm256_shuffle_epi32(b2,_MM_PERM_CDAB);constautoaw6=_mm256_alignr_epi8(aa,awa,8);constautoaw5=_mm256_alignr_epi8(aa,awa,4);res00=_mm256_add_epi64(res00,_mm256_mul_epu32(aw6,b2));res01=_mm256_add_epi64(res01,_mm256_mul_epu32(aw7,b2));res10=_mm256_add_epi64(res10,_mm256_mul_epu32(aw5,b3));res11=_mm256_add_epi64(res11,_mm256_mul_epu32(aw6,b3));constautob4=_mm256_permute4x64_epi64(bb,0xaa),b5=_mm256_shuffle_epi32(b4,_MM_PERM_CDAB);constautoaw3=_mm256_alignr_epi8(awa,aw,12);res00=_mm256_add_epi64(res00,_mm256_mul_epu32(awa,b4));res01=_mm256_add_epi64(res01,_mm256_mul_epu32(aw5,b4));res10=_mm256_add_epi64(res10,_mm256_mul_epu32(aw3,b5));res11=_mm256_add_epi64(res11,_mm256_mul_epu32(awa,b5));constautob6=_mm256_permute4x64_epi64(bb,0xff),b7=_mm256_shuffle_epi32(b6,_MM_PERM_CDAB);constautoaw2=_mm256_alignr_epi8(awa,aw,8);constautoaw1=_mm256_alignr_epi8(awa,aw,4);res00=_mm256_add_epi64(res00,_mm256_mul_epu32(aw2,b6));res01=_mm256_add_epi64(res01,_mm256_mul_epu32(aw3,b6));res10=_mm256_add_epi64(res10,_mm256_mul_epu32(aw1,b7));res11=_mm256_add_epi64(res11,_mm256_mul_epu32(aw2,b7));res00=_mm256_add_epi64(res00,res10);res01=_mm256_add_epi64(res01,res11);returnshrk32(reduce(res00,res01,Niv,Mod),Mod2);}inlinevoidvector_convolution_direct(I256*f,constI256*g,idtlm,constFNTT32_info*constinfo){u32RR=info->one;constautomod=info->mod,niv=info->niv;constautoFx=_mm256_set1_epi32(mul_s((mod-((mod-1)>>(__builtin_ctzll(lm)))),info->r3,niv,mod));constautoNiv=_mm256_set1_epi32(niv),Mod=_mm256_set1_epi32(mod),Mod2=_mm256_set1_epi32(info->mod2);for(idti=0;i<lm;++i){store256(f+i,convolve8(f+i,g+i,_mm256_set1_epi32(RR),Fx,Niv,Mod,Mod2));RR=mul(RR,info->RT1[__builtin_ctzll(~i)],niv,mod);}}inlinevoidvector_convolution_accumulate(I256*constresult,constI256*constf,constI256*constg,idtlm,constFNTT32_info*constinfo){u32RR=info->one;constautomod=info->mod,niv=info->niv;constautoFx=_mm256_set1_epi32(mul_s((mod-((mod-1)>>(__builtin_ctzll(lm)))),info->r3,niv,mod));constautoNiv=_mm256_set1_epi32(niv),Mod=_mm256_set1_epi32(mod),Mod2=_mm256_set1_epi32(info->mod2);for(idti=0;i<lm;++i){constautoproduct=convolve8(f+i,g+i,_mm256_set1_epi32(RR),Fx,Niv,Mod,Mod2);store256(result+i,add32(load256(result+i),product,Mod2));RR=mul(RR,info->RT1[__builtin_ctzll(~i)],niv,mod);}}}// namespace fast998_v2}// namespace internal}// namespace fps}// namespace m1une#endif // M1UNE_FPS_HAS_X86_SIMD
#line 24 "math/fps/convolution.hpp"
#ifdef M1UNE_FPS_HAS_X86_SIMD
#pragma GCC pop_options
#endif
#line 29 "math/fps/convolution.hpp"
namespacem1une{namespacefps{namespaceinternal{template<classMint,class=void>structhas_static_modulus:std::false_type{};template<classMint>structhas_static_modulus<Mint,std::void_t<decltype(std::integral_constant<uint32_t,Mint::mod()>{})>>:std::true_type{};constexpruint32_tprimitive_root_constexpr(uint32_tmod){if(mod==2)return1;if(mod==167772161)return3;if(mod==469762049)return3;if(mod==754974721)return11;if(mod==998244353)return3;if(mod==1224736769)return3;uint32_tdivisors[32]={};intcount=0;uint32_tx=mod-1;for(uint32_tp=2;uint64_t(p)*p<=x;p++){if(x%p!=0)continue;divisors[count++]=p;while(x%p==0)x/=p;}if(x>1)divisors[count++]=x;for(uint32_tg=2;;g++){boolok=true;for(inti=0;i<count;i++){uint64_tvalue=1;uint64_tbase=g;uint32_texponent=(mod-1)/divisors[i];while(exponent>0){if(exponent&1)value=value*base%mod;base=base*base%mod;exponent>>=1;}if(value==1){ok=false;break;}}if(ok)returng;}}constexprinttwo_adic_order(uint32_tx){intresult=0;while((x&1)==0){x>>=1;result++;}returnresult;}template<classMint>structNttRoots{staticconstexprintmax_base=two_adic_order(Mint::mod()-1);std::array<Mint,max_base+1>root;std::array<Mint,max_base+1>inverse_root;std::array<Mint,max_base>rate;std::array<Mint,max_base>inverse_rate;std::array<Mint,max_base>rate_radix4;std::array<Mint,max_base>inverse_rate_radix4;NttRoots(){constexpruint32_tprimitive_root=primitive_root_constexpr(Mint::mod());for(intlevel=1;level<=max_base;level++){root[level]=Mint(primitive_root).pow((Mint::mod()-1)>>level);inverse_root[level]=root[level].inv();}Mintproduct=1;Mintinverse_product=1;for(inti=0;i+1<max_base;i++){rate[i]=root[i+2]*product;inverse_rate[i]=inverse_root[i+2]*inverse_product;product*=inverse_root[i+2];inverse_product*=root[i+2];}product=1;inverse_product=1;for(inti=0;i+2<max_base;i++){rate_radix4[i]=root[i+3]*product;inverse_rate_radix4[i]=inverse_root[i+3]*inverse_product;product*=inverse_root[i+3];inverse_product*=root[i+3];}}};template<classMint>constNttRoots<Mint>&ntt_roots(){staticconstNttRoots<Mint>roots;returnroots;}template<classMint>voidntt(std::vector<Mint>&a,boolinverse,boolnormalize=true){constintn=int(a.size());assert(n>0&&(n&(n-1))==0);assert((Mint::mod()-1)%uint32_t(n)==0);constauto&roots=ntt_roots<Mint>();constintheight=two_adic_order(uint32_t(n));if(!inverse){intphase=0;while(phase<height){if(height-phase==1){constintwidth=1<<(height-phase-1);Minttwiddle=1;for(intblock=0;block<(1<<phase);block++){constintoffset=block<<(height-phase);for(inti=0;i<width;i++){constMintleft=a[offset+i];constMintright=a[offset+i+width]*twiddle;a[offset+i]=left+right;a[offset+i+width]=left-right;}if(block+1!=(1<<phase))twiddle*=roots.rate[__builtin_ctz(~uint32_t(block))];}phase++;continue;}constintwidth=1<<(height-phase-2);Minttwiddle=1;constMintimaginary=roots.root[2];for(intblock=0;block<(1<<phase);block++){constMinttwiddle2=twiddle*twiddle;constMinttwiddle3=twiddle2*twiddle;constintoffset=block<<(height-phase);for(inti=0;i<width;i++){constuint64_tmod2=uint64_t(Mint::mod())*Mint::mod();constuint64_ta0=a[offset+i].val();constuint64_ta1=uint64_t(a[offset+i+width].val())*twiddle.val();constuint64_ta2=uint64_t(a[offset+i+2*width].val())*twiddle2.val();constuint64_ta3=uint64_t(a[offset+i+3*width].val())*twiddle3.val();constuint64_ta1na3i=uint64_t(Mint(a1+mod2-a3).val())*imaginary.val();constuint64_tnegative_a2=mod2-a2;a[offset+i]=Mint(a0+a2+a1+a3);a[offset+i+width]=Mint(a0+a2+2*mod2-a1-a3);a[offset+i+2*width]=Mint(a0+negative_a2+a1na3i);a[offset+i+3*width]=Mint(a0+negative_a2+mod2-a1na3i);}if(block+1!=(1<<phase))twiddle*=roots.rate_radix4[__builtin_ctz(~uint32_t(block))];}phase+=2;}}else{intphase=height;while(phase>0){if(phase==1){constintwidth=1<<(height-phase);Minttwiddle=1;for(intblock=0;block<(1<<(phase-1));block++){constintoffset=block<<(height-phase+1);for(inti=0;i<width;i++){constMintleft=a[offset+i];constMintright=a[offset+i+width];a[offset+i]=left+right;a[offset+i+width]=(left-right)*twiddle;}if(block+1!=(1<<(phase-1)))twiddle*=roots.inverse_rate[__builtin_ctz(~uint32_t(block))];}phase--;continue;}constintwidth=1<<(height-phase);Minttwiddle=1;constMintinverse_imaginary=roots.inverse_root[2];for(intblock=0;block<(1<<(phase-2));block++){constMinttwiddle2=twiddle*twiddle;constMinttwiddle3=twiddle2*twiddle;constintoffset=block<<(height-phase+2);for(inti=0;i<width;i++){constuint64_ta0=a[offset+i].val();constuint64_ta1=a[offset+i+width].val();constuint64_ta2=a[offset+i+2*width].val();constuint64_ta3=a[offset+i+3*width].val();constuint64_ta2na3i=uint64_t(Mint((Mint::mod()+a2-a3)*inverse_imaginary.val()).val());a[offset+i]=Mint(a0+a1+a2+a3);a[offset+i+width]=Mint((a0+Mint::mod()-a1+a2na3i)*twiddle.val());a[offset+i+2*width]=Mint((a0+a1+2ULL*Mint::mod()-a2-a3)*twiddle2.val());a[offset+i+3*width]=Mint((a0+Mint::mod()-a1+Mint::mod()-a2na3i)*twiddle3.val());}if(block+1!=(1<<(phase-2)))twiddle*=roots.inverse_rate_radix4[__builtin_ctz(~uint32_t(block))];}phase-=2;}if(normalize){constMintinverse_n=Mint(n).inv();for(Mint&value:a)value*=inverse_n;}}}#ifdef M1UNE_FPS_HAS_X86_SIMD
#pragma GCC push_options
#pragma GCC target("avx2,bmi")
template<classMint>__attribute__((target("avx2,bmi"),hot))std::vector<Mint>convolution_998244353_simd(conststd::vector<Mint>&a,conststd::vector<Mint>&b){constintresult_size=int(a.size()+b.size()-1);intn=1;while(n<result_size)n<<=1;constboolsquaring=&a==&b;auto*transformed_a=static_cast<uint32_t*>(::operatornew[](sizeof(uint32_t)*n,std::align_val_t(32)));auto*transformed_b=squaring?transformed_a:static_cast<uint32_t*>(::operatornew[](sizeof(uint32_t)*n,std::align_val_t(32)));ifconstexpr(std::is_same_v<Mint,math::ModInt<998244353>>){static_assert(sizeof(Mint)==sizeof(uint32_t)&&std::is_trivially_copyable_v<Mint>);std::memcpy(transformed_a,a.data(),sizeof(uint32_t)*a.size());if(!squaring)std::memcpy(transformed_b,b.data(),sizeof(uint32_t)*b.size());}else{for(inti=0;i<int(a.size());i++)transformed_a[i]=a[i].val();if(!squaring)for(inti=0;i<int(b.size());i++)transformed_b[i]=b[i].val();}std::memset(transformed_a+a.size(),0,sizeof(uint32_t)*(n-a.size()));if(!squaring)std::memset(transformed_b+b.size(),0,sizeof(uint32_t)*(n-b.size()));staticconstexprfast998_v2::FNTT32_infotransform(998244353);conststd::size_tvector_size=std::size_t(n)>>3;fast998_v2::vector_dif(reinterpret_cast<__m256i*>(transformed_a),vector_size,&transform);if(!squaring)fast998_v2::vector_dif(reinterpret_cast<__m256i*>(transformed_b),vector_size,&transform);fast998_v2::vector_convolution_direct(reinterpret_cast<__m256i*>(transformed_a),reinterpret_cast<const__m256i*>(transformed_b),vector_size,&transform);fast998_v2::vector_dit<true>(reinterpret_cast<__m256i*>(transformed_a),vector_size,&transform);std::vector<Mint>result(result_size);for(intj=0;j<result_size;j++)result[j]=Mint::raw(transformed_a[j]);::operatordelete[](transformed_a,std::align_val_t(32));if(!squaring)::operatordelete[](transformed_b,std::align_val_t(32));returnresult;}#pragma GCC pop_options
#endif
}// namespace internaltemplate<classMint>std::vector<Mint>convolution_naive(conststd::vector<Mint>&a,conststd::vector<Mint>&b){if(a.empty()||b.empty())return{};std::vector<Mint>result(a.size()+b.size()-1);if(a.size()<b.size()){for(inti=0;i<int(a.size());i++){for(intj=0;j<int(b.size());j++)result[i+j]+=a[i]*b[j];}}else{for(intj=0;j<int(b.size());j++){for(inti=0;i<int(a.size());i++)result[i+j]+=a[i]*b[j];}}returnresult;}template<classMint>std::vector<Mint>convolution_ntt(conststd::vector<Mint>&a,conststd::vector<Mint>&b){constintresult_size=int(a.size()+b.size()-1);intn=1;while(n<result_size)n<<=1;assert((Mint::mod()-1)%uint32_t(n)==0);#ifdef M1UNE_FPS_HAS_X86_SIMD
ifconstexpr(Mint::mod()==998244353){if(n>=64&&__builtin_cpu_supports("avx2"))returninternal::convolution_998244353_simd(a,b);}#endif
// Allocate the padded buffers directly. Constructing from the inputs and// then resizing used to allocate and copy both large operands twice.constboolsquaring=&a==&b;std::vector<Mint>fa(n);std::copy(a.begin(),a.end(),fa.begin());internal::ntt(fa,false);constMintinverse_n=Mint(n).inv();if(squaring){for(inti=0;i<n;i++)fa[i]*=fa[i]*inverse_n;}else{std::vector<Mint>fb(n);std::copy(b.begin(),b.end(),fb.begin());internal::ntt(fb,false);for(inti=0;i<n;i++)fa[i]*=fb[i]*inverse_n;}internal::ntt(fa,true,false);fa.resize(result_size);returnfa;}namespaceinternal{template<classMint>std::vector<Mint>convolution_998244353_blocked_scalar(conststd::vector<Mint>&a,conststd::vector<Mint>&b,inttransform_size){assert(Mint::mod()==998244353);assert(transform_size>=2&&(transform_size&(transform_size-1))==0);assert((Mint::mod()-1)%uint32_t(transform_size)==0);constintblock_size=transform_size/2;constinta_blocks=int((a.size()+block_size-1)/block_size);constintb_blocks=int((b.size()+block_size-1)/block_size);autotransform_blocks=[&](conststd::vector<Mint>&values,intblock_count){std::vector<std::vector<Mint>>blocks;blocks.reserve(block_count);for(intblock=0;block<block_count;block++){constintbegin=block*block_size;constintcount=std::min(block_size,int(values.size())-begin);std::vector<Mint>transformed(transform_size);std::copy_n(values.begin()+begin,count,transformed.begin());ntt(transformed,false);blocks.emplace_back(std::move(transformed));}returnblocks;};std::vector<std::vector<Mint>>transformed_a=transform_blocks(a,a_blocks);std::vector<std::vector<Mint>>transformed_b=transform_blocks(b,b_blocks);constintresult_size=int(a.size()+b.size()-1);std::vector<Mint>result(result_size);std::vector<Mint>transformed_result(transform_size);for(intdiagonal=0;diagonal<a_blocks+b_blocks-1;diagonal++){std::fill(transformed_result.begin(),transformed_result.end(),Mint(0));constintfirst_a=std::max(0,diagonal-(b_blocks-1));constintlast_a=std::min(a_blocks-1,diagonal);for(inta_block=first_a;a_block<=last_a;a_block++){constintb_block=diagonal-a_block;for(inti=0;i<transform_size;i++)transformed_result[i]+=transformed_a[a_block][i]*transformed_b[b_block][i];}ntt(transformed_result,true);constintoutput_offset=diagonal*block_size;constintoutput_count=std::min(transform_size,result_size-output_offset);for(inti=0;i<output_count;i++)result[output_offset+i]+=transformed_result[i];}returnresult;}#ifdef M1UNE_FPS_HAS_X86_SIMD
classAlignedUint32Buffer{private:uint32_t*data_;public:explicitAlignedUint32Buffer(std::size_tsize):data_(static_cast<uint32_t*>(::operatornew[](sizeof(uint32_t)*size,std::align_val_t(32)))){}AlignedUint32Buffer(constAlignedUint32Buffer&)=delete;AlignedUint32Buffer&operator=(constAlignedUint32Buffer&)=delete;AlignedUint32Buffer(AlignedUint32Buffer&&other)noexcept:data_(other.data_){other.data_=nullptr;}AlignedUint32Buffer&operator=(AlignedUint32Buffer&&other)noexcept{if(this==&other)return*this;::operatordelete[](data_,std::align_val_t(32));data_=other.data_;other.data_=nullptr;return*this;}~AlignedUint32Buffer(){::operatordelete[](data_,std::align_val_t(32));}uint32_t*data(){returndata_;}constuint32_t*data()const{returndata_;}};template<classMint>__attribute__((target("avx2,bmi"),hot))std::vector<Mint>convolution_998244353_blocked_simd(conststd::vector<Mint>&a,conststd::vector<Mint>&b,inttransform_size){assert(Mint::mod()==998244353);assert(transform_size>=64&&(transform_size&(transform_size-1))==0);assert((Mint::mod()-1)%uint32_t(transform_size)==0);constintblock_size=transform_size/2;constinta_blocks=int((a.size()+block_size-1)/block_size);constintb_blocks=int((b.size()+block_size-1)/block_size);staticconstexprfast998_v2::FNTT32_infotransform(998244353);conststd::size_tvector_size=std::size_t(transform_size)/8;autotransform_blocks=[&](conststd::vector<Mint>&values,intblock_count){std::vector<AlignedUint32Buffer>blocks;blocks.reserve(block_count);for(intblock=0;block<block_count;block++){constintbegin=block*block_size;constintcount=std::min(block_size,int(values.size())-begin);AlignedUint32Buffertransformed(transform_size);ifconstexpr(std::is_same_v<Mint,math::ModInt<998244353>>){static_assert(sizeof(Mint)==sizeof(uint32_t)&&std::is_trivially_copyable_v<Mint>);std::memcpy(transformed.data(),values.data()+begin,sizeof(uint32_t)*count);}else{for(inti=0;i<count;i++)transformed.data()[i]=values[begin+i].val();}std::memset(transformed.data()+count,0,sizeof(uint32_t)*(transform_size-count));fast998_v2::vector_dif(reinterpret_cast<__m256i*>(transformed.data()),vector_size,&transform);blocks.emplace_back(std::move(transformed));}returnblocks;};std::vector<AlignedUint32Buffer>transformed_a=transform_blocks(a,a_blocks);std::vector<AlignedUint32Buffer>transformed_b=transform_blocks(b,b_blocks);constintresult_size=int(a.size()+b.size()-1);std::vector<Mint>result(result_size);AlignedUint32Buffertransformed_result(transform_size);for(intdiagonal=0;diagonal<a_blocks+b_blocks-1;diagonal++){std::memset(transformed_result.data(),0,sizeof(uint32_t)*transform_size);constintfirst_a=std::max(0,diagonal-(b_blocks-1));constintlast_a=std::min(a_blocks-1,diagonal);for(inta_block=first_a;a_block<=last_a;a_block++){constintb_block=diagonal-a_block;fast998_v2::vector_convolution_accumulate(reinterpret_cast<__m256i*>(transformed_result.data()),reinterpret_cast<const__m256i*>(transformed_a[a_block].data()),reinterpret_cast<const__m256i*>(transformed_b[b_block].data()),vector_size,&transform);}fast998_v2::vector_dit<true>(reinterpret_cast<__m256i*>(transformed_result.data()),vector_size,&transform);constintoutput_offset=diagonal*block_size;constintoutput_count=std::min(transform_size,result_size-output_offset);for(inti=0;i<output_count;i++){uint32_tvalue=result[output_offset+i].val()+transformed_result.data()[i];if(value>=Mint::mod())value-=Mint::mod();result[output_offset+i]=Mint::raw(value);}}returnresult;}#endif
template<classMint>std::vector<Mint>convolution_998244353_blocked(conststd::vector<Mint>&a,conststd::vector<Mint>&b,inttransform_size=1<<23){#ifdef M1UNE_FPS_HAS_X86_SIMD
if(transform_size>=64&&__builtin_cpu_supports("avx2"))returnconvolution_998244353_blocked_simd(a,b,transform_size);#endif
returnconvolution_998244353_blocked_scalar(a,b,transform_size);}}// namespace internaltemplate<classMint>std::vector<Mint>convolution(conststd::vector<Mint>&a,conststd::vector<Mint>&b){if(a.empty()||b.empty())return{};if(std::min(a.size(),b.size())<=32)returnconvolution_naive(a,b);constintresult_size=int(a.size()+b.size()-1);intn=1;while(n<result_size)n<<=1;ifconstexpr(internal::has_static_modulus<Mint>::value){ifconstexpr(Mint::mod()==998244353){if(n>(1<<23))returninternal::convolution_998244353_blocked(a,b);}if((Mint::mod()-1)%uint32_t(n)==0)returnconvolution_ntt(a,b);}usingMint1=math::ModInt<167772161>;usingMint2=math::ModInt<469762049>;usingMint3=math::ModInt<754974721>;assert(n<=(1<<24));[[maybe_unused]]constunsigned__int128coefficient_bound=static_cast<unsigned__int128>(std::min(a.size(),b.size()))*(Mint::mod()-1)*(Mint::mod()-1);[[maybe_unused]]constunsigned__int128crt_modulus=static_cast<unsigned__int128>(Mint1::mod())*Mint2::mod()*Mint3::mod();assert(coefficient_bound<crt_modulus);autoconverted_convolution=[&]<classOtherMint>(){std::vector<OtherMint>converted_a(a.size());std::vector<OtherMint>converted_b(b.size());for(inti=0;i<int(a.size());i++)converted_a[i]=OtherMint(a[i].val());for(inti=0;i<int(b.size());i++)converted_b[i]=OtherMint(b[i].val());returnconvolution_ntt(converted_a,converted_b);};std::vector<Mint1>c1=converted_convolution.templateoperator()<Mint1>();std::vector<Mint2>c2=converted_convolution.templateoperator()<Mint2>();std::vector<Mint3>c3=converted_convolution.templateoperator()<Mint3>();staticconstuint64_tinverse_mod1_mod2=Mint2(Mint1::mod()).inv().val();staticconstuint64_tmod1_mod3=Mint1::mod()%Mint3::mod();staticconstuint64_tmod1_mod2_mod3=mod1_mod3*(Mint2::mod()%Mint3::mod())%Mint3::mod();staticconstuint64_tinverse_mod1_mod2_mod3=Mint3(uint32_t(mod1_mod2_mod3)).inv().val();constuint64_ttarget_mod=Mint::mod();constuint64_tmod1_target=Mint1::mod()%target_mod;constuint64_tmod1_mod2_target=mod1_target*(Mint2::mod()%target_mod)%target_mod;std::vector<Mint>result(result_size);for(inti=0;i<result_size;i++){constuint64_tr1=c1[i].val();constuint64_tr2=c2[i].val();constuint64_tr3=c3[i].val();constuint64_tfirst=(r2+Mint2::mod()-r1%Mint2::mod())%Mint2::mod()*inverse_mod1_mod2%Mint2::mod();constuint64_tcombined_mod3=(r1%Mint3::mod()+mod1_mod3*(first%Mint3::mod()))%Mint3::mod();constuint64_tsecond=(r3+Mint3::mod()-combined_mod3)%Mint3::mod()*inverse_mod1_mod2_mod3%Mint3::mod();uint64_tvalue=r1%target_mod;value=(value+mod1_target*(first%target_mod))%target_mod;value=(value+mod1_mod2_target*(second%target_mod))%target_mod;result[i]=Mint::raw(uint32_t(value));}returnresult;}}// namespace fps}// namespace m1une#ifdef M1UNE_FPS_HAS_X86_SIMD
#undef M1UNE_FPS_HAS_X86_SIMD
#endif
#line 1 "math/primitive_root.hpp"
#line 6 "math/primitive_root.hpp"
#include<numeric>
#line 9 "math/primitive_root.hpp"
#line 1 "math/prime_factorization.hpp"
#line 10 "math/prime_factorization.hpp"
namespacem1une{namespacemath{namespaceinternal{inlineuint64_tmultiply_mod(uint64_ta,uint64_tb,uint64_tmod){returnstatic_cast<uint64_t>(static_cast<unsigned__int128>(a)*b%mod);}inlineuint64_tpower_mod(uint64_tbase,uint64_texponent,uint64_tmod){uint64_tresult=1;while(exponent>0){if(exponent&1)result=multiply_mod(result,base,mod);base=multiply_mod(base,base,mod);exponent>>=1;}returnresult;}inlineuint64_tpollard_random(){staticuint64_tstate=0x123456789abcdef0ULL;state+=0x9e3779b97f4a7c15ULL;uint64_tvalue=state;value=(value^(value>>30))*0xbf58476d1ce4e5b9ULL;value=(value^(value>>27))*0x94d049bb133111ebULL;returnvalue^(value>>31);}}// namespace internalinlineboolis_prime(uint64_tvalue){if(value<2)returnfalse;for(uint64_tprime:{2ULL,3ULL,5ULL,7ULL,11ULL,13ULL,17ULL,19ULL,23ULL,29ULL,31ULL,37ULL}){if(value%prime==0)returnvalue==prime;}uint64_todd_part=value-1;intpower_of_two=0;while((odd_part&1)==0){odd_part>>=1;power_of_two++;}for(uint64_tbase:{2ULL,325ULL,9375ULL,28178ULL,450775ULL,9780504ULL,1795265022ULL}){if(base%value==0)continue;uint64_tx=internal::power_mod(base%value,odd_part,value);if(x==1||x==value-1)continue;boolcomposite=true;for(inti=1;i<power_of_two;i++){x=internal::multiply_mod(x,x,value);if(x==value-1){composite=false;break;}}if(composite)returnfalse;}returntrue;}namespaceinternal{inlineuint64_tpollard_rho(uint64_tvalue){for(uint64_tprime:{2ULL,3ULL,5ULL,7ULL,11ULL,13ULL,17ULL,19ULL,23ULL,29ULL,31ULL,37ULL}){if(value%prime==0)returnprime;}while(true){constuint64_tconstant=pollard_random()%(value-1)+1;uint64_ty=pollard_random()%(value-1)+1;uint64_tx=0;uint64_tsaved_y=0;uint64_tgcd=1;uint64_tsegment_length=1;autoadvance=[&](uint64_tcurrent){returnstatic_cast<uint64_t>((static_cast<unsigned__int128>(multiply_mod(current,current,value))+constant)%value);};while(gcd==1){x=y;for(uint64_ti=0;i<segment_length;i++)y=advance(y);for(uint64_toffset=0;offset<segment_length&&gcd==1;offset+=128){saved_y=y;uint64_tproduct=1;constuint64_tblock=std::min<uint64_t>(128,segment_length-offset);for(uint64_ti=0;i<block;i++){y=advance(y);constuint64_tdifference=x>y?x-y:y-x;product=multiply_mod(product,difference,value);}gcd=std::gcd(product,value);}segment_length<<=1;}if(gcd==value){do{saved_y=advance(saved_y);constuint64_tdifference=x>saved_y?x-saved_y:saved_y-x;gcd=std::gcd(difference,value);}while(gcd==1);}if(gcd!=value)returngcd;}}inlinevoidfactor_recursively(uint64_tvalue,std::vector<uint64_t>&factors){if(value==1)return;if(is_prime(value)){factors.push_back(value);return;}constuint64_tdivisor=pollard_rho(value);factor_recursively(divisor,factors);factor_recursively(value/divisor,factors);}}// namespace internalinlinestd::vector<uint64_t>prime_factors(uint64_tvalue){assert(value>=1);std::vector<uint64_t>result;internal::factor_recursively(value,result);std::sort(result.begin(),result.end());returnresult;}inlinestd::vector<std::pair<uint64_t,int>>prime_factorize(uint64_tvalue){std::vector<uint64_t>factors=prime_factors(value);std::vector<std::pair<uint64_t,int>>result;for(uint64_tprime:factors){if(result.empty()||result.back().first!=prime){result.emplace_back(prime,1);}else{result.back().second++;}}returnresult;}inlinestd::vector<uint64_t>divisors(uint64_tvalue){std::vector<uint64_t>result={1};for(constauto&factor:prime_factorize(value)){constintcurrent_size=int(result.size());uint64_tpower=1;for(intexponent=1;exponent<=factor.second;exponent++){power*=factor.first;for(inti=0;i<current_size;i++){result.push_back(result[i]*power);}}}std::sort(result.begin(),result.end());returnresult;}inlineuint64_teuler_phi(uint64_tvalue){assert(value>=1);uint64_tresult=value;for(constauto&factor:prime_factorize(value)){result=result/factor.first*(factor.first-1);}returnresult;}inlineintmobius(uint64_tvalue){assert(value>=1);intresult=1;for(constauto&factor:prime_factorize(value)){if(factor.second>=2)return0;result=-result;}returnresult;}}// namespace math}// namespace m1une#line 11 "math/primitive_root.hpp"
namespacem1une{namespacemath{inlineboolhas_primitive_root(uint64_tmod){if(mod==2||mod==4)returntrue;if(mod<2)returnfalse;uint64_todd_part=mod;if((odd_part&1)==0){odd_part>>=1;if((odd_part&1)==0)returnfalse;}returnprime_factorize(odd_part).size()==1;}// Returns the smallest positive primitive root modulo mod.// Returns 0 when no primitive root exists.inlineuint64_tprimitive_root(uint64_tmod){assert(mod>=2);if(mod==2)return1;if(!has_primitive_root(mod))return0;constuint64_tphi=euler_phi(mod);conststd::vector<std::pair<uint64_t,int>>factors=prime_factorize(phi);for(uint64_tcandidate=2;candidate<mod;candidate++){if(std::gcd(candidate,mod)!=1)continue;boolgenerator=true;for(constauto&factor:factors){if(internal::power_mod(candidate,phi/factor.first,mod)==1){generator=false;break;}}if(generator)returncandidate;}return0;}}// namespace math}// namespace m1une#line 14 "math/multivariate_convolution.hpp"
namespacem1une{namespacemath{namespaceinternal{template<classT>structnested_vector_traits{usingscalar_type=T;staticconstexprintdepth=0;};template<classT,classAllocator>structnested_vector_traits<std::vector<T,Allocator>>{usingscalar_type=typenamenested_vector_traits<T>::scalar_type;staticconstexprintdepth=nested_vector_traits<T>::depth+1;};template<classNested>voidnested_vector_shape(constNested&values,std::vector<int>&shape){ifconstexpr(nested_vector_traits<Nested>::depth>0){assert(!values.empty());assert(values.size()<=std::size_t(std::numeric_limits<int>::max()));shape.push_back(int(values.size()));nested_vector_shape(values.front(),shape);}}template<classNested,classMint>voidflatten_nested_vector(constNested&values,conststd::vector<int>&shape,intlevel,std::vector<Mint>&flattened){ifconstexpr(nested_vector_traits<Nested>::depth==0){flattened.push_back(values);}else{assert(level<int(shape.size()));assert(int(values.size())==shape[level]);for(constauto&child:values){flatten_nested_vector(child,shape,level+1,flattened);}}}template<classNested,classMint>voidrebuild_nested_vector(Nested&values,conststd::vector<int>&shape,intlevel,conststd::vector<Mint>&flattened,int&position){ifconstexpr(nested_vector_traits<Nested>::depth==0){assert(position<int(flattened.size()));values=flattened[position++];}else{assert(level<int(shape.size()));values.resize(shape[level]);for(auto&child:values){rebuild_nested_vector(child,shape,level+1,flattened,position);}}}template<classNested>std::vector<int>flatten_multivariate_inputs(constNested&first,constNested&second,std::vector<typenamenested_vector_traits<Nested>::scalar_type>&flattened_first,std::vector<typenamenested_vector_traits<Nested>::scalar_type>&flattened_second){std::vector<int>shape;nested_vector_shape(first,shape);assert(int(shape.size())==nested_vector_traits<Nested>::depth);std::vector<int>second_shape;nested_vector_shape(second,second_shape);assert(second_shape==shape);flatten_nested_vector(first,shape,0,flattened_first);flatten_nested_vector(second,shape,0,flattened_second);std::reverse(shape.begin(),shape.end());returnshape;}template<classNested>Nestedrebuild_multivariate_result(std::vector<int>dimensions,conststd::vector<typenamenested_vector_traits<Nested>::scalar_type>&flattened){std::reverse(dimensions.begin(),dimensions.end());Nestedresult;intposition=0;rebuild_nested_vector(result,dimensions,0,flattened,position);assert(position==int(flattened.size()));returnresult;}inlineintmultivariate_coefficient_count(conststd::vector<int>&dimensions){int64_tcount=1;for(intdimension:dimensions){assert(dimension>0);count*=dimension;assert(count<=std::numeric_limits<int>::max());}returnint(count);}inlinestd::vector<int>multivariate_colors(conststd::vector<int>&dimensions){constintvariable_count=int(dimensions.size());constintcoefficient_count=multivariate_coefficient_count(dimensions);std::vector<int>color(coefficient_count);if(variable_count==0)returncolor;for(intindex=0;index<coefficient_count;index++){intsum=0;intstride=1;for(intvariable=0;variable+1<variable_count;variable++){stride*=dimensions[variable];sum+=index/stride;}color[index]=sum%variable_count;}returncolor;}template<classMint>std::vector<Mint>geometric_evaluation(conststd::vector<Mint>&polynomial,Mintratio){constintsize=int(polynomial.size());if(size<=64){std::vector<Mint>result(size);Mintpoint=1;for(inti=0;i<size;i++){Mintpower=1;for(constMint&coefficient:polynomial){result[i]+=coefficient*power;power*=point;}point*=ratio;}returnresult;}autotriangular_powers=[](Mintbase,intlength){std::vector<Mint>result(length);if(length==0)returnresult;result[0]=1;Mintpower=1;for(inti=0;i+1<length;i++){result[i+1]=result[i]*power;power*=base;}returnresult;};std::vector<Mint>positive=triangular_powers(ratio,2*size-1);std::vector<Mint>negative=triangular_powers(ratio.inv(),size);std::vector<Mint>scaled(polynomial);for(inti=0;i<size;i++)scaled[i]*=negative[i];std::reverse(scaled.begin(),scaled.end());std::vector<Mint>product=fps::convolution(scaled,positive);std::vector<Mint>result(size);for(inti=0;i<size;i++)result[i]=product[size-1+i]*negative[i];returnresult;}template<classMint>std::vector<Mint>cyclic_fourier_transform(std::vector<Mint>values,Mintratio,boolinverse){ifconstexpr(fps::internal::has_static_modulus<Mint>::value){constintsize=int(values.size());if((size&(size-1))==0){// Keep normalization outside the per-axis transforms, matching// the arbitrary-length DFT path below.fps::internal::ntt(values,inverse,false);returnvalues;}}returngeometric_evaluation(values,ratio);}}// namespace internaltemplate<classMint>std::vector<Mint>multivariate_convolution_truncated(conststd::vector<int>&dimensions,conststd::vector<Mint>&first,conststd::vector<Mint>&second){static_assert(fps::internal::has_static_modulus<Mint>::value,"truncated multivariate convolution requires a static-modulus type");constintvariable_count=int(dimensions.size());constintcoefficient_count=internal::multivariate_coefficient_count(dimensions);assert(int(first.size())==coefficient_count);assert(int(second.size())==coefficient_count);if(variable_count==0)return{first[0]*second[0]};int64_ttransform_size_64=1;while(transform_size_64<2LL*coefficient_count-1)transform_size_64<<=1;assert(transform_size_64<=std::numeric_limits<int>::max());constinttransform_size=int(transform_size_64);assert((Mint::mod()-1)%uint32_t(transform_size)==0);conststd::vector<int>color=internal::multivariate_colors(dimensions);std::vector<std::vector<Mint>>transformed_first(variable_count,std::vector<Mint>(transform_size));std::vector<std::vector<Mint>>transformed_second(variable_count,std::vector<Mint>(transform_size));for(inti=0;i<coefficient_count;i++){transformed_first[color[i]][i]=first[i];transformed_second[color[i]][i]=second[i];}for(intgroup=0;group<variable_count;group++){fps::internal::ntt(transformed_first[group],false);fps::internal::ntt(transformed_second[group],false);}std::vector<std::vector<Mint>>transformed_result(variable_count,std::vector<Mint>(transform_size));for(intleft=0;left<variable_count;left++){for(intright=0;right<variable_count;right++){std::vector<Mint>&destination=transformed_result[(left+right)%variable_count];conststd::vector<Mint>&left_values=transformed_first[left];conststd::vector<Mint>&right_values=transformed_second[right];for(inti=0;i<transform_size;i++){destination[i]+=left_values[i]*right_values[i];}}}for(intgroup=0;group<variable_count;group++){fps::internal::ntt(transformed_result[group],true);}std::vector<Mint>result(coefficient_count);for(inti=0;i<coefficient_count;i++){result[i]=transformed_result[color[i]][i];}returnresult;}template<classNested,std::enable_if_t<(internal::nested_vector_traits<Nested>::depth>0),int>=0>Nestedmultivariate_convolution_truncated(constNested&first,constNested&second){usingMint=typenameinternal::nested_vector_traits<Nested>::scalar_type;std::vector<Mint>flattened_first,flattened_second;std::vector<int>dimensions=internal::flatten_multivariate_inputs(first,second,flattened_first,flattened_second);std::vector<Mint>flattened_result=multivariate_convolution_truncated(dimensions,flattened_first,flattened_second);returninternal::rebuild_multivariate_result<Nested>(std::move(dimensions),flattened_result);}template<classMint>std::vector<Mint>multivariate_convolution_cyclic(conststd::vector<int>&dimensions,conststd::vector<Mint>&first,conststd::vector<Mint>&second){constintcoefficient_count=internal::multivariate_coefficient_count(dimensions);assert(int(first.size())==coefficient_count);assert(int(second.size())==coefficient_count);if(dimensions.empty())return{first[0]*second[0]};constuint32_tmodulus=Mint::mod();boolhas_all_roots=true;for(intdimension:dimensions){if((modulus-1)%uint32_t(dimension)!=0)has_all_roots=false;}if(!has_all_roots){std::vector<int>reduced_dimensions;for(intdimension:dimensions){if(dimension!=1)reduced_dimensions.push_back(dimension);}if(reduced_dimensions.empty())return{first[0]*second[0]};std::vector<int>widened_dimensions(reduced_dimensions.size());for(inti=0;i<int(reduced_dimensions.size());i++){constint64_twidened=2LL*reduced_dimensions[i]-1;assert(widened<=std::numeric_limits<int>::max());widened_dimensions[i]=int(widened);}constintwidened_count=internal::multivariate_coefficient_count(widened_dimensions);// The largest embedded input index uses coordinate dimension - 1 on// every axis. Its double is widened_count - 1, so convolving arrays// ending at this index produces exactly the widened mixed-radix box.// In particular, fps::convolution chooses the smallest transform that// contains widened_count coefficients, instead of one that contains// 2 * widened_count - 1 coefficients due to trailing zeroes.int64_tmaximum_embedded_index=0;int64_twidened_stride=1;for(intvariable=0;variable<int(reduced_dimensions.size());variable++){maximum_embedded_index+=int64_t(reduced_dimensions[variable]-1)*widened_stride;widened_stride*=widened_dimensions[variable];}assert(widened_stride==widened_count);assert(2*maximum_embedded_index+1==widened_count);assert(maximum_embedded_index<std::numeric_limits<int>::max());constintembedded_input_count=int(maximum_embedded_index)+1;std::vector<Mint>widened_first(embedded_input_count);std::vector<Mint>widened_second(embedded_input_count);for(intindex=0;index<coefficient_count;index++){intremaining=index;intwidened_index=0;intembedding_stride=1;for(intvariable=0;variable<int(reduced_dimensions.size());variable++){constintcoordinate=remaining%reduced_dimensions[variable];remaining/=reduced_dimensions[variable];widened_index+=coordinate*embedding_stride;embedding_stride*=widened_dimensions[variable];}widened_first[widened_index]=first[index];widened_second[widened_index]=second[index];}std::vector<Mint>widened_product=fps::convolution(widened_first,widened_second);assert(int(widened_product.size())==widened_count);std::vector<Mint>result(coefficient_count);for(intwidened_index=0;widened_index<widened_count;widened_index++){intremaining=widened_index;intindex=0;intstride=1;for(intvariable=0;variable<int(reduced_dimensions.size());variable++){constintcoordinate=remaining%widened_dimensions[variable];remaining/=widened_dimensions[variable];index+=(coordinate%reduced_dimensions[variable])*stride;stride*=reduced_dimensions[variable];}result[index]+=widened_product[widened_index];}returnresult;}constuint64_tgenerator=primitive_root(modulus);assert(generator!=0);std::vector<Mint>transformed_first(first);std::vector<Mint>transformed_second(second);intstride=1;for(intdimension:dimensions){assert((modulus-1)%uint32_t(dimension)==0);constMintroot=Mint(generator).pow((modulus-1)/dimension);for(intblock=0;block<coefficient_count;block+=stride*dimension){for(intoffset=0;offset<stride;offset++){std::vector<Mint>first_line(dimension);std::vector<Mint>second_line(dimension);for(inti=0;i<dimension;i++){first_line[i]=transformed_first[block+offset+stride*i];second_line[i]=transformed_second[block+offset+stride*i];}first_line=internal::cyclic_fourier_transform(std::move(first_line),root,false);second_line=internal::cyclic_fourier_transform(std::move(second_line),root,false);for(inti=0;i<dimension;i++){transformed_first[block+offset+stride*i]=first_line[i];transformed_second[block+offset+stride*i]=second_line[i];}}}stride*=dimension;}for(inti=0;i<coefficient_count;i++){transformed_first[i]*=transformed_second[i];}stride=1;for(intdimension:dimensions){constMintinverse_root=Mint(generator).pow((modulus-1)/dimension).inv();for(intblock=0;block<coefficient_count;block+=stride*dimension){for(intoffset=0;offset<stride;offset++){std::vector<Mint>line(dimension);for(inti=0;i<dimension;i++){line[i]=transformed_first[block+offset+stride*i];}line=internal::cyclic_fourier_transform(std::move(line),inverse_root,true);for(inti=0;i<dimension;i++){transformed_first[block+offset+stride*i]=line[i];}}}stride*=dimension;}constMintinverse_size=Mint(coefficient_count).inv();for(Mint&value:transformed_first)value*=inverse_size;returntransformed_first;}template<classNested,std::enable_if_t<(internal::nested_vector_traits<Nested>::depth>0),int>=0>Nestedmultivariate_convolution_cyclic(constNested&first,constNested&second){usingMint=typenameinternal::nested_vector_traits<Nested>::scalar_type;std::vector<Mint>flattened_first,flattened_second;std::vector<int>dimensions=internal::flatten_multivariate_inputs(first,second,flattened_first,flattened_second);std::vector<Mint>flattened_result=multivariate_convolution_cyclic(dimensions,flattened_first,flattened_second);returninternal::rebuild_multivariate_result<Nested>(std::move(dimensions),flattened_result);}}// namespace math}// namespace m1une#line 1 "utilities/fast_io.hpp"
#line 6 "utilities/fast_io.hpp"
#include<cerrno>
#include<charconv>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#line 13 "utilities/fast_io.hpp"
#include<iterator>
#include<string>
#include<sys/stat.h>
#line 18 "utilities/fast_io.hpp"
#include<unistd.h>
#line 20 "utilities/fast_io.hpp"
namespacem1une{namespaceutilities{structFastOutput;namespaceinternal{// Shared with the convenience helpers in template.hpp.inlineFastOutput*standard_output_instance=nullptr;// Detect std::begin(x), std::end(x).template<classT,class=void>structis_range:std::false_type{};template<classT>structis_range<T,std::void_t<decltype(std::begin(std::declval<T&>())),decltype(std::end(std::declval<T&>()))>>:std::true_type{};template<classT>inlineconstexprboolis_range_v=is_range<T>::value;template<classT>usingrange_reference_t=decltype(*std::begin(std::declval<T&>()));template<classT>usingrange_value_t=std::remove_cv_t<std::remove_reference_t<range_reference_t<T>>>;template<classT,class=void>structrange_stored_value{usingtype=range_value_t<T>;};template<classT>structrange_stored_value<T,std::void_t<typenamestd::remove_cv_t<std::remove_reference_t<T>>::value_type>>{usingtype=typenamestd::remove_cv_t<std::remove_reference_t<T>>::value_type;};template<classT>usingrange_stored_value_t=typenamerange_stored_value<T>::type;// Treat strings and C strings as scalar output objects, not as ranges.template<classT>structis_char_array:std::false_type{};template<classT,std::size_tN>structis_char_array<T[N]>:std::bool_constant<std::is_same_v<std::remove_cv_t<T>,char>>{};template<classT>structis_string_like:std::bool_constant<std::is_same_v<std::decay_t<T>,std::string>||std::is_same_v<std::decay_t<T>,constchar*>||std::is_same_v<std::decay_t<T>,char*>||is_char_array<std::remove_reference_t<T>>::value>{};template<classT>inlineconstexprboolis_string_like_v=is_string_like<T>::value;// ModInt-like type: x.val() is printable, and x can be assigned from long long.template<classT,class=void>structhas_val_method:std::false_type{};template<classT>structhas_val_method<T,std::void_t<decltype(std::declval<constT&>().val())>>:std::true_type{};template<classT>inlineconstexprboolhas_val_method_v=has_val_method<T>::value;template<classT,class=void>structhas_static_mod_raw:std::false_type{};template<classT>structhas_static_mod_raw<T,std::void_t<decltype(T::mod()),decltype(T::raw(std::declval<uint32_t>()))>>:std::true_type{};template<classT>inlineconstexprboolhas_static_mod_raw_v=has_static_mod_raw<T>::value;// libstdc++ before GCC 16 does not classify __int128 as an integral type in// strict ISO modes such as -std=c++23. Keep the fast-I/O interface independent// of that implementation detail.template<classT>inlineconstexprboolis_integral_v=std::is_integral_v<T>||std::is_same_v<std::remove_cv_t<T>,__int128_t>||std::is_same_v<std::remove_cv_t<T>,__uint128_t>;template<classT>inlineconstexprboolis_signed_v=std::is_signed_v<T>||std::is_same_v<std::remove_cv_t<T>,__int128_t>;template<classT>structmake_unsigned{usingtype=std::make_unsigned_t<T>;};template<>structmake_unsigned<__int128_t>{usingtype=__uint128_t;};template<>structmake_unsigned<__uint128_t>{usingtype=__uint128_t;};template<classT>usingmake_unsigned_t=typenamemake_unsigned<std::remove_cv_t<T>>::type;}// namespace internalstructFastInput{staticconstexprintbuffer_size=1<<20;private:std::FILE*_stream;char_buffer[buffer_size];int_position;int_length;int_file_descriptor;bool_streaming;boolrefill(){_position=0;if(_streaming){ssize_tlength;do{length=::read(_file_descriptor,_buffer,buffer_size);}while(length<0&&errno==EINTR);if(length<=0){_length=0;returnfalse;}_length=int(length);}else{_length=int(std::fread(_buffer,1,buffer_size,_stream));}return_length!=0;}template<classT>boolread_integer_from_stream(T&value){if(!skip_spaces())returnfalse;intc=read_char_raw();boolnegative=false;if(c=='-'){negative=true;c=read_char_raw();}ifconstexpr(internal::is_signed_v<T>){Tresult=0;while('0'<=c&&c<='9'){result=negative?result*10-(c-'0'):result*10+(c-'0');c=read_char_raw();}value=result;}else{Tresult=0;while('0'<=c&&c<='9'){result=result*10+T(c-'0');c=read_char_raw();}value=negative?T(0)-result:result;}returntrue;}boolprepare_number(){if(_length-_position>=64)returntrue;constintremaining=_length-_position;if(remaining>0)std::memmove(_buffer,_buffer+_position,remaining);constintadded=int(std::fread(_buffer+remaining,1,buffer_size-remaining,_stream));_position=0;_length=remaining+added;if(_length<buffer_size)_buffer[_length]='\0';return_length!=0;}public:explicitFastInput(std::FILE*stream=stdin):_stream(stream),_position(0),_length(0),_file_descriptor(::fileno(stream)),_streaming([&]{structstatstatus;return_file_descriptor>=0&&::fstat(_file_descriptor,&status)==0&&!S_ISREG(status.st_mode);}()){}FastInput(constFastInput&)=delete;FastInput&operator=(constFastInput&)=delete;intread_char_raw(){if(_position==_length&&!refill())returnEOF;return_buffer[_position++];}boolskip_spaces(){intc=read_char_raw();while(c!=EOF&&c<=' ')c=read_char_raw();if(c==EOF)returnfalse;--_position;returntrue;}boolread(char&value){if(!skip_spaces())returnfalse;value=char(read_char_raw());returntrue;}boolread(std::string&value){if(!skip_spaces())returnfalse;value.clear();while(true){constintbegin=_position;while(_position<_length&&static_cast<unsignedchar>(_buffer[_position])>' '){++_position;}value.append(_buffer+begin,_position-begin);if(_position<_length){++_position;returntrue;}if(!refill())returntrue;}}boolread(bool&value){intx;if(!read(x))returnfalse;value=x!=0;returntrue;}template<classT>std::enable_if_t<internal::is_integral_v<T>&&!std::is_same_v<std::remove_cv_t<T>,bool>&&!std::is_same_v<std::remove_cv_t<T>,char>,bool>read(T&value){if(_streaming)returnread_integer_from_stream(value);if(!prepare_number())returnfalse;intc=static_cast<unsignedchar>(_buffer[_position++]);while(c<=' ')c=static_cast<unsignedchar>(_buffer[_position++]);boolnegative=false;if(c=='-'){negative=true;c=static_cast<unsignedchar>(_buffer[_position++]);}ifconstexpr(internal::is_signed_v<T>){Tresult=0;while('0'<=c&&c<='9'){constintfirst=c-'0';constintsecond=static_cast<unsignedchar>(_buffer[_position])-'0';if(0<=second&&second<=9){result=negative?result*100-(first*10+second):result*100+(first*10+second);++_position;}else{result=negative?result*10-first:result*10+first;}c=static_cast<unsignedchar>(_buffer[_position++]);}value=result;}else{Tresult=0;while('0'<=c&&c<='9'){constunsignedfirst=unsigned(c-'0');constintsecond=static_cast<unsignedchar>(_buffer[_position])-'0';if(0<=second&&second<=9){result=result*100+T(first*10+unsigned(second));++_position;}else{result=result*10+T(first);}c=static_cast<unsignedchar>(_buffer[_position++]);}value=negative?T(0)-result:result;}if(_position>_length)_position=_length;returntrue;}template<classT>std::enable_if_t<std::is_floating_point_v<T>,bool>read(T&value){if(!skip_spaces())returnfalse;intc=read_char_raw();boolnegative=false;if(c=='-'||c=='+'){negative=c=='-';c=read_char_raw();}longdoubleresult=0;while('0'<=c&&c<='9'){result=result*10+(c-'0');c=read_char_raw();}if(c=='.'){longdoubleplace=0.1L;c=read_char_raw();while('0'<=c&&c<='9'){result+=(c-'0')*place;place*=0.1L;c=read_char_raw();}}if(c=='e'||c=='E'){c=read_char_raw();boolexponent_negative=false;if(c=='-'||c=='+'){exponent_negative=c=='-';c=read_char_raw();}intexponent=0;while('0'<=c&&c<='9'){exponent=exponent*10+(c-'0');c=read_char_raw();}longdoublescale=1;longdoublepower=10;while(exponent>0){if(exponent&1)scale*=power;power*=power;exponent>>=1;}result=exponent_negative?result/scale:result*scale;}value=static_cast<T>(negative?-result:result);returntrue;}template<classT>std::enable_if_t<internal::has_val_method_v<T>&&!internal::is_integral_v<T>&&!internal::is_range_v<T>,bool>read(T&value){longlongx;if(!read(x))returnfalse;ifconstexpr(internal::has_static_mod_raw_v<T>){if(x>=0&&uint64_t(x)<uint64_t(T::mod())){value=T::raw(uint32_t(x));}else{value=T(x);}}else{value=T(x);}returntrue;}template<classFirst,classSecond>boolread(std::pair<First,Second>&value){if(!read(value.first))returnfalse;returnread(value.second);}template<classRange>std::enable_if_t<internal::is_range_v<Range>&&!internal::is_string_like_v<Range>,bool>read(Range&range){usingStoredValue=internal::range_stored_value_t<Range>;constexprboolnested=internal::is_range_v<StoredValue>&&!internal::is_string_like_v<StoredValue>;for(auto&&value:range){ifconstexpr(std::is_same_v<StoredValue,bool>&&!nested){boolx;if(!read(x))returnfalse;value=x;}else{if(!read(value))returnfalse;}}returntrue;}template<classFirst,classSecond,class...Rest>boolread(First&first,Second&second,Rest&...rest){if(!read(first))returnfalse;returnread(second,rest...);}template<classT>FastInput&operator>>(T&value){if(!read(value))std::abort();return*this;}};structFastOutput{staticconstexprintbuffer_size=1<<20;private:inlinestaticconstautodigit_quads=[]{std::array<char,40000>result{};for(inti=0;i<10000;i++){intvalue=i;for(intj=3;j>=0;j--){result[4*i+j]=char('0'+value%10);value/=10;}}returnresult;}();std::FILE*_stream;char_buffer[buffer_size];int_position;int_precision;std::chars_format_float_format;char_range_separator;std::string*_capture=nullptr;template<classT>std::stringformat_cell(constT&value){std::stringresult;structCaptureGuard{std::string*⌖std::string*previous;~CaptureGuard(){target=previous;}}guard{_capture,_capture};_capture=&result;write(value);returnresult;}template<classMatrix>voidwrite_aligned_matrix(constMatrix&matrix){std::vector<std::vector<std::string>>rows;std::vector<std::size_t>widths;for(constauto&row:matrix){auto&cells=rows.emplace_back();std::size_tcolumn=0;for(constauto&value:row){cells.push_back(format_cell(value));if(column==widths.size())widths.push_back(0);widths[column]=std::max(widths[column],cells.back().size());++column;}}boolfirst=true;for(constauto&row:rows){if(!first)write_char('\n');first=false;for(std::size_tcolumn=0;column<row.size();++column){if(column!=0)write_char(_range_separator);for(std::size_tpadding=row[column].size();padding<widths[column];++padding){write_char(' ');}write(row[column]);}}}public:explicitFastOutput(std::FILE*stream=stdout):_stream(stream),_position(0),_precision(6),_float_format(std::chars_format::general),_range_separator(' '){if(_stream==stdout&&internal::standard_output_instance==nullptr){internal::standard_output_instance=this;}}FastOutput(constFastOutput&)=delete;FastOutput&operator=(constFastOutput&)=delete;~FastOutput(){flush();if(internal::standard_output_instance==this){internal::standard_output_instance=nullptr;}}voidflush(){if(_position!=0){std::fwrite(_buffer,1,_position,_stream);_position=0;}std::fflush(_stream);}voidwrite_char(charc){if(_capture!=nullptr){_capture->push_back(c);return;}if(_position==buffer_size)flush();_buffer[_position++]=c;}voidwrite(constchar*s){while(*s!='\0')write_char(*s++);}voidwrite(conststd::string&s){if(_capture!=nullptr){_capture->append(s);return;}std::size_tposition=0;while(position<s.size()){if(_position==buffer_size)flush();conststd::size_tcopied=std::min<std::size_t>(buffer_size-_position,s.size()-position);std::memcpy(_buffer+_position,s.data()+position,copied);_position+=int(copied);position+=copied;}}voidwrite(charc){write_char(c);}voidwrite(boolvalue){write_char(value?'1':'0');}template<classT>std::enable_if_t<std::is_floating_point_v<T>>write(Tvalue){chardigits[128];auto[end,error]=std::to_chars(digits,digits+sizeof(digits),value,_float_format,_precision);if(error!=std::errc())std::abort();for(constchar*pointer=digits;pointer!=end;pointer++){write_char(*pointer);}}template<classT>std::enable_if_t<internal::is_integral_v<T>&&!std::is_same_v<std::remove_cv_t<T>,bool>&&!std::is_same_v<std::remove_cv_t<T>,char>>write(Tvalue){usingRaw=std::remove_cv_t<T>;usingUnsigned=internal::make_unsigned_t<Raw>;Unsignedmagnitude;ifconstexpr(internal::is_signed_v<Raw>){if(value<0){write_char('-');magnitude=Unsigned(0)-Unsigned(value);}else{magnitude=Unsigned(value);}}else{magnitude=value;}if(magnitude==0){write_char('0');return;}unsignedchunks[16];intcount=0;while(magnitude>=10000){constUnsignedquotient=magnitude/10000;chunks[count++]=unsigned(magnitude-quotient*10000);magnitude=quotient;}if(_capture==nullptr&&_position>buffer_size-64)flush();charcaptured[64];char*constbegin=_capture!=nullptr?captured:_buffer+_position;char*destination=begin;constunsignedleading=unsigned(magnitude);constchar*first=digit_quads.data()+4*leading;intskip=leading<10?3:leading<100?2:leading<1000?1:0;for(;skip<4;skip++)*destination++=first[skip];while(count--){constchar*digits=digit_quads.data()+4*chunks[count];std::memcpy(destination,digits,4);destination+=4;}if(_capture!=nullptr){_capture->append(begin,destination-begin);}else{_position+=int(destination-begin);}}template<classT>std::enable_if_t<internal::has_val_method_v<T>&&!internal::is_integral_v<T>&&!internal::is_range_v<T>>write(constT&value){write(value.val());}template<classFirst,classSecond>voidwrite(conststd::pair<First,Second>&value){write(value.first);write_char(' ');write(value.second);}template<classRange>std::enable_if_t<internal::is_range_v<Range>&&!internal::is_string_like_v<Range>>write(constRange&range){usingStoredValue=internal::range_stored_value_t<constRange>;constexprboolnested=internal::is_range_v<StoredValue>&&!internal::is_string_like_v<StoredValue>;boolfirst=true;for(constauto&value:range){if(!first)write_char(nested?'\n':_range_separator);first=false;ifconstexpr(std::is_same_v<StoredValue,bool>&&!nested){write(static_cast<bool>(value));}else{write(value);}}}template<classFirst,class...Rest>voidprint(constFirst&first,constRest&...rest){write(first);((write_char(' '),write(rest)),...);}voidprintln(){write_char('\n');}voidset_precision(intprecision){_precision=precision;}voidset_fixed(intprecision=6){_float_format=std::chars_format::fixed;_precision=precision;}voidset_general(intprecision=6){_float_format=std::chars_format::general;_precision=precision;}voidset_range_separator(charseparator){_range_separator=separator;}template<classMatrix>voidwrite_aligned(constMatrix&matrix){usingRow=internal::range_stored_value_t<constMatrix>;usingCell=internal::range_stored_value_t<constRow>;static_assert(internal::is_range_v<Row>&&!internal::is_string_like_v<Row>,"write_aligned requires a two-dimensional range");static_assert(!internal::is_range_v<Cell>||internal::is_string_like_v<Cell>,"write_aligned requires scalar cells");write_aligned_matrix(matrix);}template<classMatrix>voidprintln_aligned(constMatrix&matrix){write_aligned(matrix);write_char('\n');}template<class...Args>voidprintln(constArgs&...args){print(args...);write_char('\n');}template<classT>FastOutput&operator<<(constT&value){write(value);return*this;}};}// namespace utilities}// namespace m1une#line 12 "verify/math/multivariate_convolution_cyclic.test.cpp"
namespace{usingmint=m1une::math::DynamicModInt<0>;template<classMint>std::vector<Mint>naive(conststd::vector<int>&dimensions,conststd::vector<Mint>&first,conststd::vector<Mint>&second){constintsize=int(first.size());std::vector<Mint>result(size);for(intleft=0;left<size;left++){for(intright=0;right<size;right++){intleft_index=left;intright_index=right;inttarget=0;intstride=1;for(intdimension:dimensions){constintcoordinate=(left_index%dimension+right_index%dimension)%dimension;target+=stride*coordinate;stride*=dimension;left_index/=dimension;right_index/=dimension;}result[target]+=first[left]*second[right];}}returnresult;}template<classMint>voidtest_fixed_mod_randomized(uint64_tseed){uint64_tstate=seed;autorandom=[&state](){state^=state<<7;state^=state>>9;returnstate;};constintdimensions_to_test[]={1,2,3,4,5,7,8};for(inttrial=0;trial<120;trial++){constintvariable_count=int(random()%5);std::vector<int>dimensions(variable_count);intsize=1;for(int&dimension:dimensions){dimension=dimensions_to_test[random()%7];size*=dimension;}if(size>140){trial--;continue;}std::vector<Mint>first(size),second(size);for(Mint&value:first)value=random()%Mint::mod();for(Mint&value:second)value=random()%Mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}}voidtest_randomized(){mint::set_mod(97);uint64_tstate=0xfedcba987654321ULL;autorandom=[&state](){state^=state<<7;state^=state>>9;returnstate;};constintdimensions_to_test[]={1,2,3,4,5,6,7,8};for(inttrial=0;trial<300;trial++){constintvariable_count=int(random()%4);std::vector<int>dimensions(variable_count);intsize=1;for(int&dimension:dimensions){dimension=dimensions_to_test[random()%8];size*=dimension;}if(size>200){trial--;continue;}std::vector<mint>first(size),second(size);for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}std::vector<int>dimensions={96};std::vector<mint>first(96),second(96);for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));dimensions={1,5,1,7};first.assign(35,mint(0));second.assign(35,mint(0));for(mint&value:first)value=random()%mint::mod();for(mint&value:second)value=random()%mint::mod();assert(m1une::math::multivariate_convolution_cyclic(dimensions,first,second)==naive(dimensions,first,second));}voidtest_nested_vectors(){mint::set_mod(97);std::vector<std::vector<mint>>first(3,std::vector<mint>(2));std::vector<std::vector<mint>>second(3,std::vector<mint>(2));intvalue=1;for(auto&row:first){for(mint&coefficient:row)coefficient=value++;}value=7;for(auto&row:second){for(mint&coefficient:row)coefficient=value++;}std::vector<mint>flattened_first,flattened_second;for(constauto&row:first){flattened_first.insert(flattened_first.end(),row.begin(),row.end());}for(constauto&row:second){flattened_second.insert(flattened_second.end(),row.begin(),row.end());}std::vector<mint>expected=naive(std::vector<int>{2,3},flattened_first,flattened_second);constautoresult=m1une::math::multivariate_convolution_cyclic(first,second);intindex=0;for(constauto&row:result){for(mintcoefficient:row)assert(coefficient==expected[index++]);}// Dimension 5 does not divide 97 - 1, so this exercises the mixed-radix// fallback through the nested-vector overload.first.assign(5,std::vector<mint>(3));second.assign(5,std::vector<mint>(3));for(auto&row:first){for(mint&coefficient:row)coefficient=value++;}for(auto&row:second){for(mint&coefficient:row)coefficient=value++;}flattened_first.clear();flattened_second.clear();for(constauto&row:first){flattened_first.insert(flattened_first.end(),row.begin(),row.end());}for(constauto&row:second){flattened_second.insert(flattened_second.end(),row.begin(),row.end());}expected=naive(std::vector<int>{3,5},flattened_first,flattened_second);constautofallback_result=m1une::math::multivariate_convolution_cyclic(first,second);index=0;for(constauto&row:fallback_result){for(mintcoefficient:row)assert(coefficient==expected[index++]);}}}// namespaceintmain(){test_randomized();test_nested_vectors();test_fixed_mod_randomized<m1une::math::modint998244353>(0x123456789abcdefULL);test_fixed_mod_randomized<m1une::math::modint1000000007>(0x314159265358979ULL);m1une::utilities::FastInputinput;m1une::utilities::FastOutputoutput;uint32_tmodulus=1;intvariable_count=0;input.read(modulus,variable_count);mint::set_mod(modulus);std::vector<int>dimensions(variable_count);input.read(dimensions);intsize=1;for(intdimension:dimensions)size*=dimension;std::vector<mint>first(size),second(size);input.read(first);input.read(second);output.println(m1une::math::multivariate_convolution_cyclic(dimensions,first,second));}