任意模数NTT

Orz myy!

特殊模数

如果模数很特殊, (2k+1)|(p1) ( 2 k + 1 ) | ( p − 1 ) (p1)> ( p − 1 ) > DFT的长度,那么可以求出p的原根g,用 g g 代替单位复数根wn
不会原根?请点这里

一般模数

那就不能用原根代替单位复数根这种方法了。
只能用实数。
但是如果模数很大,比如 1000000007 1000000007 ,用double的话很可能爆精度。
目标:解决精度问题。
1.用中国剩余定理:
对每个模数分别做3次DFT。一般998244353 , 1004535809 ,9985661441
然后将三次的计算结果乘起来,对要求的模数取模即可。
可行条件:三个模数的乘积大于 np2 n p 2
优点:降低错误率。
缺点:做9次DFT,太慢了。常数巨大。

2.7遍DFT
将两个多项式拆一下:
Ai=aiP+bi A i = a i ∗ P + b i Bi=ciP+di B i = c i ∗ P + d i
通过乘法分配律,则
AiBi= A i ∗ B i = P(aici) P ∗ ( a i ∗ c i ) + + P(aidi+bici) + + bidi
看得出来,做4次点值,分别求出 aici,bidi,aidi,bici a i ∗ c i , b i ∗ d i , a i ∗ d i , b i ∗ c i
再做3次插值,分别对三种颜色部分求插值即可。
然而还不优。

重头戏

myy的算法中(DFT+IDFT)的次数只有4次。
看看这究竟是什么奇技淫巧吧。
一个多项式的系数表示是这样的: A(x)=Σn1i=0aixi A ( x ) = Σ i = 0 n − 1 a i x i
在做DFT的时候, ai a i 的虚部一开始都为0。很多人以为这没用。实际上并不然。
这个算法巧妙地利用了虚部的空间。
如果 ai=ki+ibi a i = k i + i ∗ b i ,又会怎么样?
考虑1次DFT搞定原来2次DFT得出的多项式A,B.

凑式子

问题来了,现在既要考虑充分利用虚部空间,又要考虑凑式子。
C(x)=Σn1i=0aixi=Σn1i=0(ki+ibi)xi C ( x ) = Σ i = 0 n − 1 a i x i = Σ i = 0 n − 1 ( k i + i ∗ b i ) x i
由于 x x 在FFT里全部都是n次单位复数根,并且在合并A0 A1 A 1 两个多项式的时候,求 yk y k yk+n/2 y k + n / 2 的式子差不多。
yk=A0(w2kn)+wknA1(w2kn) y k = A 0 ( w n 2 k ) + w n k ∗ A 1 ( w n 2 k )
yk+n/2=A0(w2k+nn)wknA1(w2k+nn) y k + n / 2 = A 0 ( w n 2 k + n ) − w n k ∗ A 1 ( w n 2 k + n )
某人就想到了共轭复数?这个地方有点难理解。
共轭复数:实部相同,虚部互为相反数的两个复数。
ai=ki+ibi a i ′ = k i + i ∗ b i
D(x)=Σn1i=0aixi=Σn1i=0(kiibi)xi D ( x ) = Σ i = 0 n − 1 a i ′ x i = Σ i = 0 n − 1 ( k i − i ∗ b i ) x i
A(x) A ( x ) B(x) B ( x ) 可以通过简单的加减乘除凑出来。
如果用1次DFT就能求出A,B,C,D,那么
C的某一项和D的某一项是否有直接关系呢?
答案是:有。
求证: C(wkn) C ( w n k ) D(wnkn) D ( w n n − k ) 互为共轭复数。
证明:
C(wkn)=Σn1i=0(ki+ibi)wkn=Σn1i=0(ki+ibi)(cos(θ)+isin(θ))=Σn1i=0kicos(θ)bisin(θ)+i(kisin(θ)+bicos(θ)) C ( w n k ) = Σ i = 0 n − 1 ( k i + i ∗ b i ) w n k = Σ i = 0 n − 1 ( k i + i ∗ b i ) ( c o s ( θ ) + i ∗ s i n ( θ ) ) = Σ i = 0 n − 1 k i ∗ c o s ( θ ) − b i ∗ s i n ( θ ) + i ∗ ( k i ∗ s i n ( θ ) + b i ∗ c o s ( θ ) )
D(wkn)=Σn1i=0(kiibi)wkn=Σn1i=0(kiibi)(cos(θ)isin(θ))=Σn1i=0kicos(θ)bisin(θ)i(kisin(θ)+bicos(θ)) D ( w n k ) = Σ i = 0 n − 1 ( k i − i ∗ b i ) w n − k = Σ i = 0 n − 1 ( k i − i ∗ b i ) ( c o s ( θ ) − i ∗ s i n ( θ ) ) = Σ i = 0 n − 1 k i ∗ c o s ( θ ) − b i ∗ s i n ( θ ) − i ∗ ( k i ∗ s i n ( θ ) + b i ∗ c o s ( θ ) )
很容易看得出结论了。
证毕。
那这样的话,对C求DFT,就相当于对A和B求DFT了。

程序实现

模板题:洛谷4245。
注意一点:传统写法: w=wwkn w = w ∗ w n k ,乘法运算的次数过多,会使得精度有大问题,建议将n个n次单位复数根用数组存储。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 262210
#define LL long long
#define DB double
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const DB pi=acos(-1);
struct Cpx{
    DB r,i;
    Cpx(){}
    Cpx(DB R,DB I){r=R,i=I;}
    Cpx operator +(const Cpx&x)const{return Cpx(r+x.r,i+x.i);}
    Cpx operator -(const Cpx &x)const{return Cpx(r-x.r,i-x.i);}
    Cpx operator *(const Cpx &x)const{return Cpx(r*x.r-i*x.i,r*x.i+i*x.r);}
}f1[N],f2[N],w[N];
int i,j,k,L,n,m,ans;
int Rev[N];
Cpx df1[N],df3[N],df4[N];
LL x2,mo;
void FFT(Cpx *h,int delta){
    int i,k,m,Half;
    fo(i,0,L-1)if(i<Rev[i])swap(h[i],h[Rev[i]]);
    for(m=2;m<=L;m<<=1){
        Half=m>>1;
        for(i=0;i<L;i+=m){
            int v=0;
            fo(k,i,i+Half-1){
                Cpx u=h[k],t=w[v]*h[k+Half];
                h[k]=u+t;
                h[k+Half]=u-t;
                v=(v+delta*(L/m)+L)&(L-1);
                //w=w*wn;//精度差很多 乘法运算次数太多 
            }
        }
    }
    if(delta==-1)fo(i,0,L-1)h[i].r/=L;
}

void Mulpoly(Cpx *x,Cpx *y,Cpx *z){
    int i,j;
    static Cpx a[N],b[N],c[N];
    fo(i,0,L-1)a[i]=Cpx((LL)x[i].r&32767,(LL)x[i].r>>15);
    fo(i,0,L-1)b[i]=Cpx((LL)y[i].r&32767,(LL)y[i].r>>15);
    FFT(a,1);
    FFT(b,1);
    fo(i,0,L-1){
        static Cpx d1,d2,d3,d4;
        j=(L-i)&(L-1);
        d1=(a[i]+Cpx(a[j].r,-a[j].i))*Cpx(0.5,0);
        d2=(a[i]-Cpx(a[j].r,-a[j].i))*Cpx(0,-0.5);
        d3=(b[i]+Cpx(b[j].r,-b[j].i))*Cpx(0.5,0);
        d4=(b[i]-Cpx(b[j].r,-b[j].i))*Cpx(0,-0.5);
        df1[i]=d1*d3;
        df3[i]=d1*d4+d2*d3;
        df4[i]=d2*d4;
    }
    fo(i,0,L-1)a[i]=df1[i]+df4[i]*Cpx(0,1);
    //巧妙利用虚部空间,一次IDFT完成红色和蓝色的插值。
    fo(i,0,L-1)b[i]=df3[i];
    FFT(a,-1);FFT(b,-1);
    fo(i,0,L-1){
        static LL d1,d3,d4;
        d1=(LL)(a[i].r+0.5)%mo;
        d3=(LL)(b[i].r+0.5)%mo;
        d4=(LL)(a[i].i/L+0.5)%mo;
        z[i].r=(((LL)d4<<30)%mo+((LL)d3<<15)%mo+d1)%mo;
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&mo);
    for(L=1;L<n+m+1;L<<=1);
    fo(i,0,L-1)Rev[i]=Rev[i>>1]>>1|(i&1)*(L>>1);
    fo(i,0,n){
        scanf("%lld",&x2);
        f1[i]=Cpx(x2,0);
    }
    fo(i,0,m){
        scanf("%lld",&x2);
        f2[i]=Cpx(x2,0);
    }
    fo(i,0,L-1)w[i]=Cpx(cos(2*pi*i/L),sin(2*pi*i/L));
    Mulpoly(f1,f2,f1);
    fo(i,0,n+m)printf("%lld ",(LL)f1[i].r);
    return 0;
}
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值