任意模数FFT:MTT

前言

注明:本文litble写给自己以后复习用,只是梳理,不做证明,学习MTT推荐下面这篇博客。

感谢这位dalao->here

MTT

将每个系数拆成 a + b M a+bM a+bM的形式,将原来的一个多项式写成两个多项式。

( a 1 + b 1 M ) ( a 2 + b 2 M ) = a 1 a 2 + a 1 b 2 M + b 1 a 2 M + b 1 b 2 M 2 (a_1+b_1M)(a_2+b_2M)=a_1a_2+a_1b_2M+b_1a_2M+b_1b_2M^2 (a1+b1M)(a2+b2M)=a1a2+a1b2M+b1a2M+b1b2M2

M = P M=\sqrt{P} M=P 时, a 1 a 2 a_1a_2 a1a2 a 1 b 2 a_1b_2 a1b2 b 1 a 2 b_1a_2 b1a2 b 1 b 2 b_1b_2 b1b2都是 P P P级别的,就可以防止炸掉。

不过这样的话,要DFT a 1 a_1 a1 a 2 a_2 a2 b 1 b_1 b1 b 2 b_2 b2这四个多项式,要IDFT a 1 a 2 a_1a_2 a1a2 a 1 b 2 + b 1 a 2 a_1b_2+b_1a_2 a1b2+b1a2 b 1 b 2 b_1b_2 b1b2,一共做了7次(虽然还是比任意模数NTT少了2次的说……),不够优秀。

于是就有大神提出了优秀的做法。

定义 P ( x ) = A ( x ) + B ( x ) i P(x)=A(x)+B(x)i P(x)=A(x)+B(x)i Q ( x ) = A ( x ) − B ( x ) i Q(x)=A(x)-B(x)i Q(x)=A(x)B(x)i

则会有 Q ( ω k ) = c o n j ( P ( ω − k ) ) = c o n j ( P ( ω n − k ) ) Q(\omega^k)=conj(P(\omega^{-k}))=conj(P(\omega^{n-k})) Q(ωk)=conj(P(ωk))=conj(P(ωnk))

(conj是取共轭复数的意思, n n n是FFT时我们把多项式的度数延伸到的那个2的次方级别的数)

于是我们只用DFT这个 P ( x ) P(x) P(x),就能得到 Q ( x ) Q(x) Q(x)被DFT之后的结果。

DFT完成后,我们还可以还原出 A A A B B B的DFT:

A ( ω k ) = P ( ω k ) + Q ( ω k ) 2 A(\omega^k)=\frac{P(\omega^k)+Q(\omega^k)}{2} A(ωk)=2P(ωk)+Q(ωk)

B ( ω k ) = − i P ( ω k ) − Q ( ω k ) 2 B(\omega^k)=-i\frac{P(\omega^k)-Q(\omega^k)}{2} B(ωk)=i2P(ωk)Q(ωk)

这样我们就可以处理出 a = a 1 a 2 a=a_1a_2 a=a1a2 b = a 1 b 2 b=a_1b_2 b=a1b2 c = b 1 a 2 c=b_1a_2 c=b1a2 d = b 1 b 2 d=b_1b_2 d=b1b2的说,注意这里的 a , b , c , d a,b,c,d a,b,c,d还都是复数的说。

然后构造 M 1 = a + b i M_1=a+bi M1=a+bi M 2 = c + d i M_2=c+di M2=c+di两个多项式,IDFT回去。则分别取这两个IDFT后的多项式的实部和虚部(第一个的实部为 a a a,虚部为 b b b以此类推),就是IDFT后的 a , b , c , d a,b,c,d a,b,c,d,节约2的常数。

这样只要做四次DFT即可。

注意本方法对精度要求很高,记得开long double

代码

洛谷P4245

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
	int q=0;char ch=' ';
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
	return q;
}
typedef long double db;//对精度要求很高
typedef long long LL;
const int N=262150,M=32767;
const db pi=acos(-1);
int n,m,P,ka[N],kb[N],kc[N],rev[N];
struct com{db r,i;}a[N],b[N],Aa[N],Ab[N],Ba[N],Bb[N];
com operator + (com A,com B) {return (com){A.r+B.r,A.i+B.i};}
com operator - (com A,com B) {return (com){A.r-B.r,A.i-B.i};}
com operator * (com A,com B) {return (com){A.r*B.r-A.i*B.i,A.r*B.i+A.i*B.r};}
com conj(com A) {return (com){A.r,-A.i};}

void FFT(com *a,int n) {
	for(RI i=0;i<n;++i) if(rev[i]>i) swap(a[i],a[rev[i]]);
	for(RI i=1;i<n;i<<=1) {
		com wn=(com){cos(pi/i),sin(pi/i)};
		for(RI j=0;j<n;j+=(i<<1)) {
			com t1,t2,w=(com){1,0};
			for(RI k=0;k<i;++k,w=w*wn)
				t1=a[j+k],t2=w*a[j+i+k],a[j+k]=t1+t2,a[j+i+k]=t1-t2;
		}
	}
}
void mul(int *ka,int *kb,int *kc,int n) {
	for(RI i=0;i<n;++i) ka[i]%=P,kb[i]%=P;
	for(RI i=0;i<n;++i)
		a[i]=(com){ka[i]&M,ka[i]>>15},b[i]=(com){kb[i]&M,kb[i]>>15};//构造出两个要相乘的多项式的P
	FFT(a,n),FFT(b,n);
	for(RI i=0;i<n;++i) {
		int j=(n-i)&(n-1);//将P还原回A,B。别漏了&(n-1)这个取模操作
		com kAa=(a[i]+conj(a[j]))*(com){0.5,0};
		com kAb=(a[i]-conj(a[j]))*(com){0,-0.5};
		com kBa=(b[i]+conj(b[j]))*(com){0.5,0};
		com kBb=(b[i]-conj(b[j]))*(com){0,-0.5};
		Aa[j]=kAa*kBa,Ab[j]=kAa*kBb,Ba[j]=kAb*kBa,Bb[j]=kAb*kBb;
		//提前将多项式翻转完成,IDFT的时候就不用翻了
	}
	for(RI i=0;i<n;++i)//构造M
		a[i]=Aa[i]+Ab[i]*(com){0,1},b[i]=Ba[i]+Bb[i]*(com){0,1};
	FFT(a,n),FFT(b,n);
	for(RI i=0;i<n;++i) {
		int kAa=(LL)(a[i].r/n+0.5)%P;//直接提取实部和虚部
		int kAb=(LL)(a[i].i/n+0.5)%P;
		int kBa=(LL)(b[i].r/n+0.5)%P;
		int kBb=(LL)(b[i].i/n+0.5)%P;
		kc[i]=((LL)kAa+((LL)(kAb+kBa)<<15)+((LL)kBb<<30))%P;
	}
}
int main()
{
	n=read(),m=read(),P=read();
	for(RI i=0;i<=n;++i) ka[i]=read();
	for(RI i=0;i<=m;++i) kb[i]=read();
	int kn=1,len=0;
	while(kn<=n+m) kn<<=1,++len;
	for(RI i=0;i<kn;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
	mul(ka,kb,kc,kn);
	for(RI i=0;i<=n+m;++i) printf("%d ",(kc[i]+P)%P);
	return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值