瞎讲:任意模数MTT

瞎讲一下。


三模数 N T T NTT NTT

大概思路就是用三个满足 a ∗ 2 b + 1 a*2^b+1 a2b+1形式的质数来做 N T T NTT NTT
然后用数论方法搞出它的具体值(当长度为 1 0 5 10^5 105级别时,卷积之后数字最多为 1 0 23 10^{23} 1023,所有同余的数中只有最小的那个在范围内)。
一般选 469762049 , 998244353 , 1004535809 469762049,998244353,1004535809 469762049,998244353,1004535809,原根都是 3 3 3
调用 9 9 9 D F T DFT DFT,常数极大。


二模数 N T T NTT NTT

和上面的那个思路差不多,只不过用一个大质数和一个小质数来搞。
long long相乘取模用强制转long double来解决。
质数取 29 ∗ 2 57 + 1 29*2^{57}+1 29257+1 998244353 998244353 998244353,原根都是 3 3 3


拆分 F F T FFT FFT

考虑将每个数拆成 a W + b aW+b aW+b的形式, W W W P \sqrt P P
结果长这样: a c W 2 + ( a d + b c ) W + b d acW^2+(ad+bc)W+bd acW2+(ad+bc)W+bd
(这里 a , b , c , d a,b,c,d a,b,c,d都应该理解成函数)
这样一个位置上的数字最大是 1 0 14 10^{14} 1014级别,精度好就可以接受。
调用 7 7 7 D F T DFT DFT


拆分 F F T FFT FFT优化

分别计算:
( a + b i ) ( c + d i ) = ( a c − b d ) + ( a d + b c ) i ( a − b i ) ( c + d i ) = ( a c + b d ) + ( a d − b c ) i (a+bi)(c+di)=(ac-bd)+(ad+bc)i \\ (a-bi)(c+di)=(ac+bd)+(ad-bc)i (a+bi)(c+di)=(acbd)+(ad+bc)i(abi)(c+di)=(ac+bd)+(adbc)i

将实部和虚部拆开来,就可以分别求出所需的 a c ac ac b d bd bd ( a d + b c ) (ad+bc) (ad+bc)
于是只需求 ( a + b i ) (a+bi) (a+bi) ( a − b i ) (a-bi) (abi) ( c + d i ) (c+di) (c+di) D F T DFT DFT,以及两个乘积要做 I D F T IDFT IDFT
这样就可以优化到 5 5 5 D F T DFT DFT

利用FFT三次变两次中提到的性质(也就是共轭的两个多项式,求出其中一个的 D F T DFT DFT之后可以 O ( n ) O(n) O(n)地求出另一个的 D F T DFT DFT)。
于是 ( a − b i ) (a-bi) (abi) D F T DFT DFT可以通过 ( a + b i ) (a+bi) (a+bi) D F T DFT DFT求。
所以只需要用 4 4 4 D F T DFT DFT
贴个代码

using namespace std;
#include 
#include 
#include 
#include 
#include 
#define N 262144
#define db double
#define ll long long
const db PI=acos(-1);
struct com{db a,b;};
inline com operator+(const com &x,const com &y){return {x.a+y.a,x.b+y.b};}
inline com operator-(const com &x,const com &y){return {x.a-y.a,x.b-y.b};}
inline com operator*(const com &x,const com &y){return {x.a*y.a-x.b*y.b,x.a*y.b+x.b*y.a};} 
inline com operator*(const com &x,db y){return {x.a*y,x.b*y};}
int re[N];
int n,m,nN,p,W;
int a[N],b[N],c[N];
com wnk[18][N];
void dft(com A[],int flag=1){
	for (int i=0;i<nN;++i)
		if (i<re[i])
			swap(A[i],A[re[i]]);
	int cnt=0;
	for (int i=1;i<nN;i<<=1,++cnt){
//		com wn={cos(PI/i),flag*sin(PI/i)};
		for (int j=0;j<nN;j+=i<<1){
//			com wnk={1,0};
			for (int k=j;k<j+i;++k/*,wnk=wnk*wn*/){
				com tmp=wnk[cnt][k-j];
				tmp.b*=flag;
				com x=A[k],y=tmp*A[k+i];
				A[k]=x+y;
				A[k+i]=x-y;
			}
		}
	}
	if (flag==-1){
		db inv=(db)1/nN;
		for (int i=0;i<nN;++i)
			A[i]=A[i]*inv;
	}
}
com A[N],B[N],C[N];
void multi(int a[],int b[],int n,int m){
	int bit=0;
	for (nN=1;nN<=n+m;nN<<=1,++bit);
	re[0]=0;
	for (int i=1;i<nN;++i)
		re[i]=re[i>>1]>>1|(i&1)<<bit-1;
	for (int i=0;i<bit;++i)
		for (int j=0;j<1<<i;++j)
			wnk[i][j]={cos(PI*j/(1<<i)),sin(PI*j/(1<<i))};
	/*
	for (int i=0;i
	W=sqrt(p);
	for (int i=0;i<nN;++i)
		A[i]={a[i]/W,a[i]%W};
	dft(A);
	/*
	for (int i=0;i
	B[0]={A[0].a,-A[0].b};
	for (int i=1;i<nN;++i)
		B[i]={A[nN-i].a,-A[nN-i].b};
	
	for (int i=0;i<nN;++i)
		C[i]={b[i]/W,b[i]%W};    
	dft(C);
	for (int i=0;i<nN;++i){
		A[i]=A[i]*C[i];
		B[i]=B[i]*C[i];
	}
	dft(A,-1),dft(B,-1);
	for (int i=0;i<nN;++i){
		ll Aa=round(A[i].a),Ab=round(A[i].b),Ba=round(B[i].a);
		ll x=((ll)((Aa+Ba)/2)%p+p)%p;
		ll y=((ll)((Ba-Aa)/2)%p+p)%p;
		ll z=((ll)(Ab)%p+p)%p;
		c[i]=(W*W*x+W*z+y)%p;
//		printf("(%.16lf->%lld,%.16lf->%lld) (%.16lf->%lld,/) %x=%lld y=%lld z=%lld c[i]=%lld\n",A[i].a,Aa,A[i].b,Ab,B[i].a,Ba,x,y,z,c[i]);
	}
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d%d%d",&n,&m,&p);
	for (int i=0;i<=n;++i)
		scanf("%d",&a[i]),a[i]%=p;
	for (int i=0;i<=m;++i)
		scanf("%d",&b[i]),b[i]%=p;
	multi(a,b,n,m);
	for (int i=0;i<=n+m;++i)
		printf("%d ",c[i]);
	return 0;
}

3.5 3.5 3.5 D F T DFT DFT

抱歉这种高级算法不配我这种蒟蒻使用。
本蒟蒻也懒得学……
感兴趣的话可以看毛啸的论文。

你可能感兴趣的:(FFT,NTT)