zoukankan      html  css  js  c++  java
  • 洛谷 P4245 [模板]任意模数NTT —— 三模数NTT / 拆系数FFT(MTT)

    题目:https://www.luogu.org/problemnew/show/P4245

    用三模数NTT做,需要注意时间和细节;

    注意各种地方要取模!传入 upt() 里面的数一定要不超过2倍 mod!

    乘法会爆 long long 时用快速乘!

    两次合并的模数,第一次是 (ll) p1*p2,第二次直接对题目的模数取模即可!

    注意局部开 (ll)!

    合并时用到的逆元每次都一样,所以要先处理好而不是现场快速幂算!!

    然而为什么时间还是 Narh 的两倍!

    一晚上的心血...

    代码如下:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    int const xn=(1<<19);
    int n,m,lim,rev[xn],a[5][xn],b[5][xn],p[5]={0,469762049,998244353,1004535809};
    ll mod;
    int rd()
    {
      int ret=0,f=1; char ch=getchar();
      while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return f?ret:-ret;
    }
    ll upt(ll x,int md){while(x>=md)x-=md; while(x<0)x+=md; return x;}
    ll mul(ll a,ll b,int md)
    {
      ll ret=0; a=a%md; b=b%md;
      if(a<0)a+=md; if(b<0)b+=md;//
      for(;b;b>>=1ll,a=(a+a)%md)if(b&1)ret=(ret+a)%md;
      return ret;
    }
    ll pw(ll a,int b,int md)
    {
      ll ret=1; a=a%md;
      for(;b;b>>=1,a=mul(a,a,md))if(b&1)ret=mul(ret,a,md);//mul!!
      return ret;
    }
    void ntt(int *a,int tp,int md)
    {
      for(int i=0;i<lim;i++)
        if(i<rev[i])swap(a[i],a[rev[i]]);
      for(int mid=1;mid<lim;mid<<=1)
        {
          int wn=pw(3,(md-1)/(mid<<1),md);
          if(tp==-1)wn=pw(wn,md-2,md);
          for(int j=0,len=(mid<<1);j<lim;j+=len)
        {
          int w=1;
          for(int k=0;k<mid;k++,w=(ll)w*wn%md)
            {
              int x=a[j+k],y=(ll)w*a[j+mid+k]%md;
              a[j+k]=upt(x+y,md); a[j+mid+k]=upt(x-y,md);
            }
        }
        }
      if(tp==1)return; int inv=pw(lim,md-2,md);
      for(int i=0;i<lim;i++)a[i]=(ll)a[i]*inv%md;
    }
    ll uni(ll r1,ll r2,ll m1,int m2,int tp,int inv)
    {
      ll k=mul(r2-r1,inv,m2);
      if(!tp)return (r1+k*m1)%(m1*m2);
      return upt((r1+mul(k,m1,mod))%mod,mod);//%mod!!
    }
    int main()
    {
      n=rd(); m=rd(); mod=rd();
      for(int i=0;i<=n;i++)a[1][i]=a[2][i]=a[3][i]=rd();
      for(int i=0;i<=m;i++)b[1][i]=b[2][i]=b[3][i]=rd();
      lim=1; int l=0;
      while(lim<=n+m+2)lim<<=1,l++;//+2!
      for(int i=0;i<lim;i++)
        rev[i]=((rev[i>>1]>>1)|((i&1)<<(l-1)));
      for(int i=1;i<=3;i++)
        {
          ntt(a[i],1,p[i]); ntt(b[i],1,p[i]);
          for(int j=0;j<lim;j++)a[i][j]=(ll)a[i][j]*b[i][j]%p[i];
          ntt(a[i],-1,p[i]);
        }
      int inv1=pw(p[1],p[2]-2,p[2]);
      int inv2=pw((ll)p[1]*p[2],p[3]-2,p[3]);//inv!!
      for(int i=0;i<=n+m;i++)
        {
          ll ans=uni(a[1][i],a[2][i],p[1],p[2],0,inv1);
          ans=uni(ans,a[3][i],(ll)p[1]*p[2],p[3],1,inv2);//(ll)!!!
          printf("%lld ",ans);
        }
      puts("");
      return 0;
    }

    关于拆系数FFT,这篇博客说得十分清晰:https://blog.csdn.net/lvzelong2014/article/details/80156989

    而且代码也十分简洁优美,所以就模仿着写了;

    注意:

    1. 读入的初始数组要先取模;

    2. 对 (x<<30) 开 (ll) 要写在括号里面而非外面;

    3. IDFT中最后要 /lim,平常都之写 a[i].x/lim,但这里因为用到了 y,所以必须加上 a[i].y/lim!!

    4. 卡精度,要开 long double

    (注意数组范围,如果要用到 a[lim] 的话,再稍微把数组开大一点)

    代码如下:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    typedef long double db;//
    int const xn=(1<<18)+5;//a[lim]
    db const Pi=acos(-1.0);
    int n,m,lim,rev[xn],P,f[xn],g[xn],ans[xn];
    struct com{db x,y;}a[xn],b[xn],Da[xn],Db[xn],Dc[xn],Dd[xn];
    com operator + (com a,com b){return (com){a.x+b.x,a.y+b.y};}
    com operator - (com a,com b){return (com){a.x-b.x,a.y-b.y};}
    com operator * (com a,com b){return (com){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
    com conj(com a){return (com){a.x,-a.y};}
    int rd()
    {
      int ret=0,f=1; char ch=getchar();
      while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return f?ret:-ret;
    }
    void init()
    {
      lim=1; int l=0;
      while(lim<=n+m)lim<<=1,l++;
      for(int i=0;i<lim;i++)rev[i]=((rev[i>>1]>>1)|((i&1)<<(l-1)));
    }
    int upt(int x){while(x>=P)x-=P; while(x<0)x+=P; return x;}
    void fft(com *a,int tp)
    {
      for(int i=0;i<lim;i++)
        if(i<rev[i])swap(a[i],a[rev[i]]);
      for(int mid=1;mid<lim;mid<<=1)
        {
          com wn=(com){cos(Pi/mid),tp*sin(Pi/mid)};
          for(int j=0,len=(mid<<1);j<lim;j+=len)
        {
          com w=(com){1,0};
          for(int k=0;k<mid;k++,w=w*wn)
            {
              com x=a[j+k],y=w*a[j+mid+k];
              a[j+k]=x+y; a[j+mid+k]=x-y;
            }
        }
        }
      if(tp==1)return;
      for(int i=0;i<lim;i++)a[i].x/=lim,a[i].y/=lim;//y!! for use y
    }
    void mul(int *A,int *B,int *C)
    {
      for(int i=0;i<lim;i++)A[i]=upt(A[i]%P),B[i]=upt(B[i]%P);//
      int M=(1<<15)-1;
      for(int i=0;i<lim;i++)a[i]=(com){A[i]&M,A[i]>>15};
      for(int i=0;i<lim;i++)b[i]=(com){B[i]&M,B[i]>>15};
      fft(a,1); fft(b,1);
      a[lim]=a[0]; b[lim]=b[0];//
      for(int i=0,j=lim;i<lim;i++,j--)
        {
          com da,db,dc,dd;
          da=(a[i]+conj(a[j]))*(com){0.5,0};
          db=(a[i]-conj(a[j]))*(com){0,-0.5};
          dc=(b[i]+conj(b[j]))*(com){0.5,0};
          dd=(b[i]-conj(b[j]))*(com){0,-0.5};
          Da[i]=da*dc; Db[i]=da*dd; Dc[i]=db*dc; Dd[i]=db*dd;
        }
      a[lim]=b[lim]=(com){0,0};
      for(int i=0;i<lim;i++)a[i]=Da[i]+Db[i]*(com){0,1};
      for(int i=0;i<lim;i++)b[i]=Dc[i]+Dd[i]*(com){0,1};
      fft(a,-1); fft(b,-1);
      for(int i=0;i<=n+m;i++)
        {
          int da=(ll)(a[i].x+0.5)%P;
          int db=(ll)(a[i].y+0.5)%P;
          int dc=(ll)(b[i].x+0.5)%P;
          int dd=(ll)(b[i].y+0.5)%P;
          C[i]=(da+((ll)(db+dc)<<15)+((ll)dd<<30))%P;//(ll)
        }
    }
    int main()
    {
      n=rd(); m=rd(); P=rd(); init();
      for(int i=0;i<=n;i++)f[i]=rd();
      for(int i=0;i<=m;i++)g[i]=rd();
      mul(f,g,ans);
      for(int i=0;i<=n+m;i++)printf("%d ",upt(ans[i])); puts("");
      return 0;
    }
  • 相关阅读:
    调试技巧--Windows端口号是否被占用
    如何制定自己的职业规划
    SQL总结(四)编辑类
    SQL总结(三)其他查询
    CompareAndSwap原子操作原理
    JVM调优之服务内存超过阈值报警
    Javassist中文技术文档
    微言Netty:分布式服务框架
    共享变量边界处理
    Netty客户端发送消息并同步获取结果
  • 原文地址:https://www.cnblogs.com/Zinn/p/10035728.html
Copyright © 2011-2022 走看看