NTT 小记
数论来力……证明之类的也许会大挂。
或者其实还好,在参考别人的证明思路之后。
Pre
注意到 FFT 中需要复数计算,原因在于涉及到了单位复数根。
有没有替代品?复数域(这是包含实数和虚数的)内暂时没有。
不过我们可以考虑在模意义下找一个。
阶与原根
下面
对于一个正整数
阶的一条性质:若
证明则是把
对于一个正整数
由于我们要用到不同
证明的话考虑
如果
然后我们看这个
两两不同。单位根定义,显然。
证明考虑取
,然后两边做商发现违背阶的最小性。消去引理。同理,和单位根那一模一样。
折半引理,这里比较棘手。
首先说明
,考虑两边分别平分,有 。显然其中 ,故成立。然后就说好了,这玩意儿显然了,也不棘手。
,要求有没有可能我们最先在整数域上学的等比数列这玩意儿。
现在我们说明了在模意义下原根跟单位根有一样好的性质,也能拿来算多项式乘法.
而这个东西由于涉及到上面一堆数学,于是就成了 NTT(快速数论变换)。
式子基本一致,把
注意我们要求 (当然你也可以取 。
代码咕了。
qL 代码封好了,放一个跑不赢 FFT 的垃圾供大家欣赏:
#include<cstdio>
#include<utility>
#include<cstdlib>
#include<type_traits>
#include<array>
/**
* 写得死烂,又长又慢。
* Author:qL
* todo:
* Better modInt
* frac
* More Poly
* fix bug of radix_sort
* new IO
*/
namespace QL{
/**
* 图方便用的
*/
namespace{
using ll=long long;
using ull=unsigned long long;
using uint=unsigned int;
using db=double;
using ld=long double;
#if _GLIBCXX_USE_INT128
using lll=__int128;
using ulll=unsigned __int128;
#else
using lll=long long;
using ulll=unsigned long long;
#endif
#if _GLIBCXX_NUMERIC&&(__cplusplus>=201703L)
template<typename _Tp>
constexpr _Tp Inf=std::numeric_limits<_Tp>::max()/2;
#else
constexpr int Inf=0x3f3f3f3f;
constexpr long long llInf=0x3f3f3f3f3f3f3f3f;
constexpr double dbInf=1e17;
constexpr long double ldInf=1e22;
#endif
#ifndef _GLIBCXX_CMATH
#define sqrt __builtin_sqrt
#define sqrtf __builtin_sqrtf
#define sqrtl __builtin_sqrtl
#define ceil __builtin_ceil
#define ceilf __builtin_ceilf
#define ceill __builtin_ceill
#define floor __builtin_floor
#define floorf __builtin_floorf
#define floorl __builtin_floorl
#define log2 __builtin_log2
#define log __builtin_log
#define cos __builtin_cos
#define sin __builtin_sin
#define tan __builtin_tan
#define acos __builtin_acos
#endif
#ifndef _GLIBCXX_CSTRING
#define memset __builtin_memset
#define memcpy __builtin_memcpy
#define strlen __builtin_strlen
#define strcmp __builtin_strcmp
#endif
#ifndef _GLIBCXX_CSTDIO
#define fwrite __builtin_fwrite
#define putchar __builtin_putchar
#define fputc __builtin_fputc
#define fputs __builtin_fputs
#endif
#ifndef likely
#define likely(x) __builtin_expect(!!(x),1)
#endif
#ifndef unlikely
#define unlikely(x) __builtin_expect(!!(x),0)
#endif
}
/**
* 不想多加头文件了……
*/
namespace Error{
constexpr void make_re(int _seed_=114514){
std::exit(_seed_);
}
#ifndef _GLIBCXX_CASSERT
constexpr bool assert(bool x,const char *reason){
if(unlikely(!x)){
fputs(reason,stderr);
fputs(reason,stdout);
make_re();
}
return false;
}
constexpr bool assert(bool x,char *reason){
return assert(x,const_cast<const char *>(reason));
}
constexpr bool assert(bool x){
if(unlikely(!x)){
fputs("Assert: RE",stderr);
fputs("Assert: RE",stdout);
make_re();
}
return false;
}
#endif
}
}
namespace QL{
/**
* 这坨代码最让人难以理解的地方:没逝乱靠元编程库
*/
namespace Traits_Tools{
template<typename _Tp>
class has_member_swap{
private:
template<typename T>
static auto Check(void)->decltype(std::declval<T>().swap(),std::true_type());
template<typename T>
static std::false_type Check(...);
public:
enum{value=std::is_same<decltype(Check<_Tp>(nullptr)),std::true_type>::value};
};
#define Operator_Check_Helper(name,opt) \
template<typename _Tp1,typename _Tp2> \
class has_operator_##name{ \
private: \
template<typename T1,typename T2> \
static auto Check(void)->decltype(std::declval<T1,T2>().operator opt (),std::true_type()); \
template<typename T1,typename T2> \
static std::false_type Check(...); \
public: \
enum{value=std::is_same<decltype(Check<_Tp1,_Tp2>(nullptr)),std::true_type>::value}; \
};
Operator_Check_Helper(plus,+)
Operator_Check_Helper(subtract,-)
Operator_Check_Helper(multiply,*)
Operator_Check_Helper(divide,/)
Operator_Check_Helper(mod,%)
Operator_Check_Helper(and,&)
Operator_Check_Helper(or,|)
Operator_Check_Helper(xor,^)
#undef Operator_Check_Helper
#define Is_Type_Helper(type) \
template<typename _Tp> \
constexpr bool is_##type =false; \
template<> \
constexpr bool is_##type < type > =true;
Is_Type_Helper(bool)
Is_Type_Helper(char)
#undef Is_Type_Helper
template<typename _Tp,
typename std::enable_if<!is_bool<_Tp>&&std::is_integral<_Tp>::value>::type* =nullptr>
struct to_signed_type;
#define To_Signed_Helper(type) \
template<> \
struct to_signed_type< unsigned type >{ \
using signed_type= type ; \
}; \
template<> \
struct to_signed_type < type >{ \
using signed_type= type; \
};
To_Signed_Helper(int)
To_Signed_Helper(long long)
#undef To_Signed_Helper
template<typename _Tp,
typename std::enable_if<!is_bool<_Tp>&&std::is_integral<_Tp>::value>::type* =nullptr>
struct to_unsigned_type;
#define To_Unsigned_Helper(type) \
template<> \
struct to_unsigned_type< type >{ \
using unsigned_type=unsigned type; \
}; \
template<> \
struct to_unsigned_type< unsigned type >{ \
using unsigned_type=unsigned type; \
};
To_Unsigned_Helper(int)
To_Unsigned_Helper(long long)
#undef To_Unsigned_Helper
template<typename _Tp,
typename std::enable_if<!is_bool<_Tp>&&!is_char<_Tp>&&std::is_integral<_Tp>::value>::type* =nullptr>
struct to_upper_type;
#define To_Upper_Helper(type,upper) \
template<> \
struct to_upper_type< type >{ \
using unsigned_type=upper; \
}; \
template<> \
struct to_upper_type< u##type >{ \
using unsigned_type=u##upper; \
};
To_Upper_Helper(int,ll)
To_Upper_Helper(ll,lll)
#undef To_Upper_Helper
}
}
namespace QL{
namespace rel_ops{
namespace Calc_Self{
#define Calc_Self_Helper(opt) \
template<typename _Tp1,typename _Tp2> \
constexpr _Tp1 &operator opt##=(_Tp1 &lhs,const _Tp2 &rhs){ \
return lhs=(lhs opt rhs); \
}
Calc_Self_Helper(+)
Calc_Self_Helper(-)
Calc_Self_Helper(*)
Calc_Self_Helper(/)
Calc_Self_Helper(%)
Calc_Self_Helper(&)
Calc_Self_Helper(|)
Calc_Self_Helper(^)
#undef Calc_Self_Helper
}
namespace Compare{
template<typename _Tp1,typename _Tp2>
constexpr bool operator!=(const _Tp1 &lhs,const _Tp2 &rhs){
return !(lhs==rhs);
}
template<typename _Tp1,typename _Tp2>
constexpr bool operator<=(const _Tp1 &lhs,const _Tp2 &rhs){
return (lhs==rhs)||(lhs<rhs);
}
template<typename _Tp1,typename _Tp2>
constexpr bool operator>(const _Tp1 &lhs,const _Tp2 &rhs){
return !((lhs==rhs)||(lhs<rhs));
}
template<typename _Tp1,typename _Tp2>
constexpr bool operator>=(const _Tp1 &lhs,const _Tp2 &rhs){
return !(lhs<rhs);
}
}
}
}
namespace QL{
namespace Base_Tools{
template<typename _Tp>
static constexpr std::size_t integer_length=sizeof(_Tp)*10/sizeof(int);
bool is_space(char ch){
return ch==' ';
}
bool is_eoln(char ch){
#if (linux||__linux__||__linux)
return ch=='\n'||ch=='\r';
#else
return ch=='\n';
#endif
}
bool is_blank(char ch){
return is_space(ch)||is_eoln(ch);
}
bool is_digit(char ch){
switch(ch){
case '0' ... '9': return true;
default: return false;
}
}
bool is_eof(char ch){
return ch==EOF;
}
}
namespace IO{
using Base_Tools::integer_length;
using Base_Tools::is_digit;
using Base_Tools::is_space;
using Base_Tools::is_eoln;
using Base_Tools::is_blank;
using Base_Tools::is_eof;
/**
* fread+fwrite,-DLOACL for debug
* support:integer,floating,string,...
* todo:other
*/
class IOstream{
protected:
using LIST=std::initializer_list<int>;
#ifndef LOCAL
std::FILE *IN;
std::FILE *OUT;
static constexpr int SIZE=1<<15;
char buf[SIZE]{},*p1{buf},*p2{buf},obuf[SIZE]{},*p3{obuf};
public:
char pull(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,SIZE,IN),p1==p2)?(Ch=EOF):*p1++;}
void push(char ch){((p3-obuf)==SIZE&&(flush(false),0)),*p3++=ch;}
template<std::size_t L>
std::FILE *set_in(const char (&name)[L]){
static char in[L+5]={};
std::sprintf(in,"%s.in",name);
return std::fopen(in,"r");
}
template<std::size_t L>
std::FILE *set_out(const char (&name)[L]){
static char out[L+5];
std::sprintf(out,"%s.out",name);
return std::fopen(out,"w");
}
#else
protected:
public:
char pull(){return std::getchar();}
void push(char ch){putchar(ch);}
void err(char ch){fputc(ch,stderr);}
template<std::size_t L>
void set_in(const char(&name)[L]){
static char in[L+5]={};
std::sprintf(in,"%s.in",name);
std::freopen(in,"r",stdin);
}
template<std::size_t L>
void set_out(const char(&name)[L]){
static char out[L+5];
std::sprintf(out,"%s.out",name);
std::freopen(out,"w",stdout);
}
#endif
public:
#ifndef LOCAL
IOstream():IN{stdin},OUT{stdout},buf{},p1{buf},p2{buf},obuf{},p3{obuf},Ch{'\n'},outchar{&IO::IOstream::push}{}
template<std::size_t L>
IOstream(const char(&name)[L]):IN{set_in(name)},OUT{set_out(name)},buf{},p1{buf},p2{buf},obuf{},p3{obuf},Ch{'\n'},outchar{&IO::IOstream::push}{}
template<std::size_t L>
IOstream(const char(&name)[L],bool in,bool out):IN{in?set_in(name):stdin},OUT{out?set_out(name):stdout},buf{},p1{buf},p2{buf},obuf{},p3{obuf},Ch{'\n'},outchar{&IO::IOstream::push}{}
template<std::size_t L>
IOstream(char(&name)[L]):IN{set_in(name)},OUT{set_out(name)},buf{},p1{buf},p2{buf},obuf{},p3{obuf},Ch{'\n'},outchar{&IO::IOstream::push}{}
template<std::size_t L>
IOstream(char(&name)[L],bool in,bool out):IN{in?set_in(name):stdin},OUT{out?set_out(name):stdout},buf{},p1{buf},p2{buf},obuf{},p3{obuf},Ch{'\n'},outchar{&IO::IOstream::push}{}
~IOstream(){flush();}
void flush(bool _flush_=false){
if(likely(p3!=obuf))
fwrite(obuf,1,p3-obuf,OUT),p3=obuf;
if(_flush_) std::fflush(stdout);
}
#else
IOstream(){}
template<std::size_t L>
IOstream(const char(&name)[L]):Ch{'\n'}{reset_stdin(name),reset_stdout(name);}
template<std::size_t L>
IOstream(const char(&name)[L],bool in,bool out):Ch{'\n'}{in&&(reset_stdin(name),0),out&&(reset_stdout(name),0);}
template<std::size_t L>
IOstream(char(&name)[L]):Ch{'\n'}{reset_stdin(name),reset_stdout(name);}
template<std::size_t L>
IOstream(char(&name)[L],bool in,bool out):Ch{'\n'}{in&&(reset_stdin(name),0),out&&(reset_stdout(name),0);}
void flush(){std::fflush(stdout);}
#endif
template<typename T>
T read(){
T x;
read(x);
return x;
}
protected:
char Ch='\n';
public:
bool eof(void)const{
return Ch==EOF;
}
template<typename T>
void read(T &&x,typename std::enable_if<std::is_integral<T>::value,void>::type* =nullptr,typename std::enable_if<std::is_signed<T>::value,void>::type* =nullptr){
x=0;bool flag=0;
for(;!is_digit(Ch)&&!is_eof(Ch);Ch=pull()) if(Ch=='-') flag=1;
if(is_eof(Ch)) return;
if(flag) for(;is_digit(Ch);Ch=pull()) x=x*10-(Ch&15);
else for(;is_digit(Ch);Ch=pull()) x=x*10+(Ch&15);
}
template<typename T>
void read(T &&x,typename std::enable_if<std::is_integral<T>::value,void>::type* =nullptr,typename std::enable_if<std::is_unsigned<T>::value,void>::type* =nullptr){
x=0;
for(;!is_digit(Ch)&&!is_eof(Ch);Ch=pull());
if(is_eof(Ch)) return;
for(;is_digit(Ch);Ch=pull()) x=x*10+(Ch&15);
}
void read(char *str){
for(;is_blank(Ch);Ch=pull());
if(is_eof(Ch)) return (void)(*str='\0');
for(;!is_blank(Ch)&&!is_eof(Ch);Ch=pull()) *str++=Ch;
*str='\0';
}
void read(char &c){
c=Ch;
for(;is_blank(c)&&!is_eof(c);c=pull());
if(is_eof(c)) return;
Ch=pull();
}
void read(bool &x){
for(;Ch!='0'&&Ch!='1'&&!is_eof(Ch);Ch=pull());
if(is_eof(Ch)) return void(x=false);
x=Ch=='1';
Ch=pull();
}
template<typename T>
void read(T &&x,typename std::enable_if<std::is_floating_point<T>::value,void*>::type* =nullptr){
static char str[114];
read(str);
x=std::atof(str);
}
template<typename T>
void read(T &x){read(std::move(x));}
protected:
void(IOstream::*outchar)(char)=&IO::IOstream::push;
template<typename T>
void out(T x,typename std::enable_if<std::is_integral<T>::value,void>::type* =nullptr,typename std::enable_if<std::is_signed<T>::value,void>::type* =nullptr){
static char sta[integer_length<T>];
int top=0;
if(x<0){
(this->*outchar)('-');
do sta[top++]=((-x)%10)|48,x/=10;
while(x);
}
else{
do sta[top++]=(x%10)|48,x/=10;
while(x);
}
while(top) (this->*outchar)(sta[--top]);
}
template<typename T>
void out(T x,typename std::enable_if<std::is_integral<T>::value,void>::type* =nullptr,typename std::enable_if<std::is_unsigned<T>::value,void>::type* =nullptr){
static char sta[integer_length<T>];
int top=0;
do sta[top++]=(x%10)|48,x/=10;
while(x);
while(top) (this->*outchar)(sta[--top]);
}
void out(bool x){(this->*outchar)(x?'1':'0');}
void out(char x){(this->*outchar)(x);}
void out(char *str){
out(reinterpret_cast<const char *>(str));
}
void out(const char *str){
while(*str!='\0') (this->*outchar)(*str++);
}
/**
* ssprintf is awful...
*/
void out(float x){
static char str[114];
std::sprintf(str,"%f",x);
out(str);
}
void out(double x){
static char str[114];
std::sprintf(str,"%f",x);
out(str);
}
void out(long double x){
static char str[114];
std::sprintf(str,"%Lf",x);
out(str);
}
void out(std::pair<int,float> x){
static char str[114],opt[10];
std::sprintf(opt,"%%.%df",x.first);
std::sprintf(str,opt,x.second);
out(str);
}
void out(std::pair<int,double> x){
static char str[114],opt[10];
std::sprintf(opt,"%%.%df",x.first);
std::sprintf(str,opt,x.second);
out(str);
}
void out(std::pair<int,long double> x){
static char str[114],opt[10];
std::sprintf(opt,"%%.%dLf",x.first);
std::sprintf(str,opt,x.second);
out(str);
}
void set_std_out(){outchar=&IO::IOstream::push;}
#ifdef LOCAL
void set_err_out(){outchar=&IO::IOstream::err;}
#endif
public:
template<typename...Args>
void read(Args &&...args){(void)LIST{(read(args),0)...};}
template<typename...Args>
void write(Args...args){set_std_out(),(void)LIST{(out(args),0)...};}
template<typename...Args>
void writeln(Args...args){write(args...),push('\n');}
#ifdef LOCAL
template<typename...Args>
void error(Args...args){set_err_out(),(void)LIST{(out(args),0)...};}
template<typename...Args>
void errorln(Args...args){error(args...),err('\n');}
#endif
};
IOstream lq;
}
}
namespace QL{
namespace Base_Tools{
template<typename _Tp1,typename _Tp2>
constexpr auto min(_Tp1 x,_Tp2 y){
return x<y?x:y;
}
template<typename _Tp,typename ...Args>
constexpr auto min(_Tp x,Args ...args){
return min(x,min(args...));
}
template<typename _Tp1,typename _Tp2>
constexpr auto max(_Tp1 x,_Tp2 y){
return y<x?x:y;
}
template<typename _Tp,typename ...Args>
constexpr auto max(_Tp x,Args ...args){
return max(x,max(args...));
}
template<typename _Tp1,typename _Tp2>
constexpr bool chkmin(_Tp1 &x,_Tp2 y){
return y<x?(x=y,true):false;
}
template<typename _Tp1,typename _Tp2,typename...Args>
constexpr bool chkmin(_Tp1 &x,_Tp2 y,Args ...args){
return chkmin(x,y)|chkmin(x,args...);
}
template<typename _Tp1,typename _Tp2>
constexpr bool chkmax(_Tp1 &x,_Tp2 y){
return x<y?(x=y,true):false;
}
template<typename _Tp1,typename _Tp2,typename...Args>
constexpr bool chkmax(_Tp1 &x,_Tp2 y,Args ...args){
return chkmax(x,y)|chkmax(x,args...);
}
template<typename _Tp,
typename std::enable_if<!Traits_Tools::has_member_swap<_Tp>::value&&!std::is_integral<_Tp>::value>::type* =nullptr>
constexpr void swap(_Tp &x,_Tp &y){
_Tp tmp;
tmp=x,x=y,y=tmp;
}
template<typename _Tp,typename std::enable_if<std::is_integral<_Tp>::value>::type* =nullptr>
constexpr void swap(_Tp &x,_Tp &y){
x!=y&&(x^=y^=x^=y);
}
template<typename _Tp>
constexpr void swap(_Tp *&x,_Tp *&y){
_Tp *tmp;
tmp=x,x=y,y=tmp;
}
template<typename _Tp,typename std::enable_if<Traits_Tools::has_member_swap<_Tp>::value>::type* =nullptr>
constexpr void swap(_Tp &x,_Tp &y){
x.swap(y);
}
template<typename _Tp>
constexpr _Tp abs(const _Tp &x){
return x<0?-x:x;
}
}
}
namespace QL{
/**
* gcd is 1 for floating
*/
namespace Math_Tools{
namespace Math_Tools_base{
template<typename _Tp,typename _Used>
constexpr _Tp qpow(ull x,_Used p,_Tp mod){
_Tp ret=1;
for(;p;p>>=1,x=x*x%mod) if(p&1) ret=ret*x%mod;
return ret;
}
}
template<typename _Tp,typename _Used>
constexpr _Tp qpow(_Tp x,_Used p,_Tp mod){
if(unlikely(x<0)) x=mod+(x%mod);
return Math_Tools_base::qpow(x,p,mod);
}
template<typename _Tp,typename _Used>
constexpr _Tp qpow(_Tp x,_Used p){
_Tp ret=1;
for(;p;p>>=1,x=x*x) if(p&1) ret=ret*x;
return ret;
}
template<typename _Tp>
constexpr _Tp inv(_Tp x,_Tp mod){
return Math_Tools_base::qpow(x,mod-2,mod);
}
template<typename _Tp>
constexpr auto gcd(_Tp a,_Tp b,typename std::enable_if<std::is_integral<_Tp>::value>::type* =nullptr){
return b?gcd(b,a%b):a;
}
template<typename _Tp>
constexpr auto gcd(_Tp a,_Tp b,typename std::enable_if<std::is_floating_point<_Tp>::value>::type* =nullptr){
return 1;
}
template<typename _Tp>
constexpr auto lcm(_Tp a,_Tp b){
return a/gcd(a,b)*b;
}
}
}
#if __cplusplus<201703L
namespace QL{
namespace Array{
template<typename _Tp,unsigned N>
class array{
private:
_Tp arr[N];
public:
constexpr unsigned size()const{return N;}
constexpr _Tp &operator[](unsigned _n){
return arr[_n];
}
constexpr _Tp operator[](unsigned _n)const{
return arr[_n];
}
};
}
}
#endif
namespace QL{
/**
* 多项式,占坑
* 以后会尽量补全的
*/
namespace Poly_Tools{
namespace Poly_Base{
constexpr uint max_g=22,max_len=2<<20;
constexpr uint pw[]{0,1,2,4,8,16,32,64,128,256,512,1024};
uint rev[max_len];
constexpr void ReInit(uint n){
for(uint i(0);i<n;++i) rev[i]=(rev[i>>1]>>1)|((i&1)?(n>>1):0);
}
constexpr uint to_up(uint x){
#define cs(x) case pw[x] ... pw[x+1]-1: return pw[x+1];
switch(x){
cs(0)cs(1)cs(2)cs(3)cs(4)cs(5)cs(6)cs(7)cs(8)cs(9)cs(10)
default: return 1u<<(32-__builtin_clz(x));
}
#undef cs
}
}
namespace ComplexNumber{
template<typename _Tp>
struct complex{
_Tp real,imag;
constexpr complex():real{},imag{}{}
template<typename T>
constexpr complex(const T &_real):real(_real),imag{}{}
template<typename T1,typename T2>
constexpr complex(const T1 &_real,const T2 &_imag):real(_real),imag(_imag){}
template<typename T>
constexpr complex(const complex<T> &it):real(it.real),imag(it.imag){}
constexpr complex operator+(const complex &it)const{
return complex(real+it.real,imag+it.imag);
}
constexpr complex operator-(const complex &it)const{
return complex(real-it.real,imag-it.imag);
}
constexpr complex operator*(const complex &it)const{
return complex(real*it.real-imag*it.imag,imag*it.real+real*it.imag);
}
};
}
namespace Primitive_Root{
#if __cplusplus<201703L
template<typename _Tp,unsigned N>
using array=QL::Array::array<_Tp,N>;
#else
template<typename _Tp,unsigned N>
using array=std::array<_Tp,N>;
#endif
template<typename _U_Tp>
static constexpr auto init_for_gn(_U_Tp p,_U_Tp g){
array<_U_Tp,Poly_Base::max_g> ret{};
for(uint i(0);i<Poly_Base::max_g;++i) ret[i]=Math_Tools::qpow(g,(p-1)>>i,p);
return ret;
}
template<typename _U_Tp>
static constexpr auto init_for_inv(_U_Tp p,_U_Tp g){
array<_U_Tp,Poly_Base::max_g> ret{};
_U_Tp v=Math_Tools::inv(g,p);
for(uint i(0);i<Poly_Base::max_g;++i) ret[i]=Math_Tools::qpow(v,(p-1)>>i,p);
return ret;
}
struct Prim_Root{};
/**
* todo:
* find the prim root
* support more types
*/
struct runtime_Prim_Root:Prim_Root{
uint p,g;
array<uint,Poly_Base::max_g> gn,inv;
runtime_Prim_Root():p{},g{},gn{},inv{}{}
runtime_Prim_Root(uint _p,uint _g):p{_p},g{_g},gn{init_for_gn(p,g)},inv{init_for_inv(g,p)}{}
};
template<typename _U_Tp,_U_Tp _p,_U_Tp _g>
struct const_Prim_Root:Prim_Root{
static constexpr _U_Tp p{_p},g{_g};
static constexpr array<_U_Tp,Poly_Base::max_g> gn{init_for_gn(_p,_g)},inv{init_for_inv(_p,_g)};
};
template<typename _U_Tp,_U_Tp _p,_U_Tp _g>
constexpr _U_Tp const_Prim_Root<_U_Tp,_p,_g>::p;
template<typename _U_Tp,_U_Tp _p,_U_Tp _g>
constexpr _U_Tp const_Prim_Root<_U_Tp,_p,_g>::g;
template<typename _U_Tp,_U_Tp _p,_U_Tp _g>
constexpr array<_U_Tp,Poly_Base::max_g> const_Prim_Root<_U_Tp,_p,_g>::gn;
template<typename _U_Tp,_U_Tp _p,_U_Tp _g>
constexpr array<_U_Tp,Poly_Base::max_g> const_Prim_Root<_U_Tp,_p,_g>::inv;
using G_998244353i=const_Prim_Root<uint,998244353,3>;
using G_1004535809i=const_Prim_Root<uint,1004535809,3>;
using G_46976204i=const_Prim_Root<uint,469762049,3>;
}
/**
* 暴力乘法(划掉)
* upd:FFT
*/
using Base_Tools::swap;
using namespace rel_ops::Calc_Self;
namespace Poly_Base{
template<typename _Tp>
class Poly{
protected:
_Tp *p;
uint n;
public:
constexpr uint length(void)const{return n;}
constexpr Poly():p{},n{}{}
constexpr Poly(const uint &_n):p{new _Tp[_n]{}},n{_n}{}
constexpr Poly(const std::initializer_list<_Tp> &L){
delete[] p;
p=new _Tp[L.size()]{},n=0;
for(auto it:L) p[n++]=it;
}
constexpr Poly(const Poly &it):n{it.n}{
p=new _Tp[it.n]{},n=it.n;
memcpy(p,it.p,sizeof(_Tp)*it.n);
}
constexpr void resize(const uint &_n){
delete[] p;
p=new _Tp[_n]{},n=_n;
}
constexpr Poly &operator=(const Poly &it){
if(this==&it) return *this;
delete[] p;
p=new _Tp[it.n]{},n=it.n;
memcpy(p,it.p,sizeof(_Tp)*it.n);
return *this;
}
constexpr _Tp &operator[](const uint &x){
if(x>=n) Error::make_re();
return p[x];
}
constexpr const _Tp &operator[](const uint &x)const{
if(x>=n) Error::make_re();
return p[x];
}
~Poly(){
delete[] p;
}
};
}
namespace Transforms{
namespace For_FFT{
using Complex=ComplexNumber::complex<db>;
Complex f[Poly_Base::max_len];
constexpr db PI=acos(-1);
constexpr void FFT_transform(Complex *f,uint n,int type){
for(uint i(0);i<n;++i) if(i<Poly_Base::rev[i]) swap(f[i],f[Poly_Base::rev[i]]);
for(uint p(1),l(2);l<=n;p=l,l<<=1){
Complex step(cos(PI/p),type*sin(PI/p));
for(uint i(0);i<n;i+=l){
Complex *g=f+i,*h=g+p,w(1,0),t;
for(uint k(0);k<p;++k,w=w*step)
t=h[k]*w,h[k]=g[k]-t,g[k]=g[k]+t;
}
}
}
}
}
template<typename _Tp>
class FFT:public Poly_Base::Poly<_Tp>{
using Poly=Poly_Base::Poly<_Tp>;
public:
constexpr FFT():Poly(){}
constexpr FFT(const Poly &poly):Poly(poly){}
constexpr Poly operator*(const FFT<_Tp> &it){
using namespace Transforms::For_FFT;
Poly ret(Poly::n+it.length()-1);
uint x(Poly_Base::to_up(ret.length()));
for(uint i(0);i<x;++i) f[i]=Complex(0,0);
for(uint i(0);i<Poly::n;++i) f[i].real=Poly::p[i];
for(uint i(0);i<it.length();++i) f[i].imag=it[i];
Poly_Base::ReInit(x);
FFT_transform(f,x,1);
for(uint i(0);i<x;++i) f[i]*=f[i];
FFT_transform(f,x,-1);
for(uint i(0);i<ret.length();++i) ret[i]=f[i].imag/x/2.0+0.5;
return ret;
}
};
namespace Transforms{
namespace For_NTT{
template<bool type,typename _Prim_Root>
constexpr void NTT_transform(ull *f,uint n,const _Prim_Root &pr){
static_assert(std::is_base_of<Primitive_Root::Prim_Root,_Prim_Root>::value,
"In NTT(transform): Prim_Root is invalid,Please use the class in QL::Primitive_Root.");
for(uint i(0);i<n;++i) if(i<Poly_Base::rev[i]) swap(f[i],f[Poly_Base::rev[i]]);
for(uint p(1),l(2),d(1);l<=n;p=l,l<<=1,++d){
ull step(type?pr.gn[d]:pr.inv[d]);
for(uint i(0);i<n;i+=l){
ull *g=f+i,*h=g+p,w(1),t;
for(uint k(0);k<p;++k,w=w*step%pr.p)
t=h[k]*w%pr.p,h[k]=(g[k]+pr.p-t)%pr.p,g[k]=(g[k]+t)%pr.p;
}
}
}
ull f[Poly_Base::max_len],g[Poly_Base::max_len];
}
};
template<typename _Tp,typename _Prim_Root=Primitive_Root::G_998244353i>
class NTT:public Poly_Base::Poly<_Tp>{
using Poly=Poly_Base::Poly<_Tp>;
static _Prim_Root pr;
static_assert(std::is_integral<_Tp>::value,
"In NTT(class): The type is not an integral.");
static_assert(std::is_base_of<Primitive_Root::Prim_Root,_Prim_Root>::value,
"In NTT(class): Prim_Root is invalid,Please use the class in QL::Primitive_Root.");
public:
constexpr NTT():Poly(){}
constexpr NTT(const Poly &poly):Poly(poly){}
constexpr Poly operator*(const NTT<_Tp,_Prim_Root> &it){
using namespace Transforms::For_NTT;
Poly ret(Poly::n+it.length()-1);
uint x(Poly_Base::to_up(ret.length()));
for(uint i(0);i<Poly::n;++i) f[i]=Poly::p[i];
for(uint i(Poly::n);i<x;++i) f[i]=0;
for(uint i(0);i<it.length();++i) g[i]=it[i];
for(uint i(it.length());i<x;++i) g[i]=0;
Poly_Base::ReInit(x);
NTT_transform<true>(f,x,pr);
NTT_transform<true>(g,x,pr);
for(uint i(0);i<x;++i) f[i]=f[i]*g[i]%pr.p;
NTT_transform<false>(f,x,pr);
for(uint i(0);i<ret.length();++i) ret[i]=f[i]*Math_Tools::qpow(x,pr.p-2,pr.p)%pr.p;
return ret;
}
};
template<typename _Tp,typename _Prim_Root>
_Prim_Root NTT<_Tp,_Prim_Root>::pr;
}
}
namespace MAIN{
/* 主函数在这捏…… */
using namespace QL;
using QL::IO::lq;
using QL::Poly_Tools::NTT;
int n,m;
NTT<int> f,g;
signed _main_(){
lq.read(n,m);
f.resize(n+1);
g.resize(m+1);
for(int i=0;i<=n;++i) lq.read(f[i]);
for(int i=0;i<=m;++i) lq.read(g[i]);
f=f*g;
for(uint i(0);i<f.length();++i) lq.write(f[i],' ');
return 0;
}
}
signed main(){
return MAIN::_main_();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!