这里会显示出您选择的修订版和当前版本之间的差别。
|
2020-2021:teams:legal_string:2021年度训练计划及训练记录:lgwza:快速数论变换_ntt [2021/02/15 21:59] lgwza 创建 |
2020-2021:teams:legal_string:2021年度训练计划及训练记录:lgwza:快速数论变换_ntt [2021/02/15 22:01] (当前版本) lgwza |
||
|---|---|---|---|
| 行 71: | 行 71: | ||
| printf("%lld ",(a[i]*inv)%P); | printf("%lld ",(a[i]*inv)%P); | ||
| | | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | </hidden> | ||
| + | |||
| + | ==== 例 2 ==== | ||
| + | |||
| + | === 题意 === | ||
| + | |||
| + | [[https://www.luogu.com.cn/problem/P1919|P1919 【模板】A*B Problem升级版(FFT快速傅里叶)]] | ||
| + | |||
| + | 给你两个正整数 $a,b$,求 $a\times b$。$1\le a,b\le 10^{1000000}$ | ||
| + | |||
| + | === 题解 === | ||
| + | |||
| + | 使用 NTT 求解本题 | ||
| + | |||
| + | === 评价 === | ||
| + | |||
| + | NTT 模板题 | ||
| + | |||
| + | === 代码 === | ||
| + | |||
| + | <hidden> | ||
| + | <code cpp> | ||
| + | #include<bits/stdc++.h> | ||
| + | using namespace std; | ||
| + | typedef long long ll; | ||
| + | const int N=1e7+5; | ||
| + | const ll P=998244353,G=3,Gi=332748118; | ||
| + | int rev[N]; | ||
| + | char s1[N],s2[N]; | ||
| + | ll a[N],b[N]; | ||
| + | int c[N]; | ||
| + | ll fastpow(ll x,ll y){ | ||
| + | ll ret=1; | ||
| + | for(;y;y>>=1,x=x*x%P) | ||
| + | if(y&1) ret=ret*x%P; | ||
| + | return ret; | ||
| + | } | ||
| + | void NTT(ll *y,int len,int on){ | ||
| + | for(int i=0;i<len;i++) | ||
| + | if(i<rev[i]) | ||
| + | swap(y[i],y[rev[i]]); | ||
| + | for(int mid=1;mid<len;mid<<=1){ | ||
| + | ll wn=fastpow(on==1?G:Gi,(P-1)/(mid<<1)); | ||
| + | for(int j=0;j<len;j+=(mid<<1)){ | ||
| + | ll w=1; | ||
| + | for(int k=0;k<mid;k++,w=w*wn%P){ | ||
| + | ll u=y[j+k],t=w*y[j+k+mid]%P; | ||
| + | y[j+k]=(u+t)%P; | ||
| + | y[j+k+mid]=(u-t+P)%P; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | int main(){ | ||
| + | scanf("%s%s",s1,s2); | ||
| + | int n=strlen(s1),m=strlen(s2); | ||
| + | n--,m--; | ||
| + | for(int i=0;i<=n;i++) a[i]=s1[n-i]-'0'; | ||
| + | for(int i=0;i<=m;i++) b[i]=s2[m-i]-'0'; | ||
| + | int len=1,l=0; | ||
| + | while(len<=n+m) len<<=1,l++; | ||
| + | for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1)); | ||
| + | NTT(a,len,1); | ||
| + | NTT(b,len,1); | ||
| + | for(int i=0;i<len;i++) a[i]=a[i]*b[i]%P; | ||
| + | NTT(a,len,-1); | ||
| + | ll inv=fastpow(len,P-2); | ||
| + | for(int i=0;i<=n+m;i++){ | ||
| + | a[i]=a[i]*inv%P; | ||
| + | c[i]+=a[i]; | ||
| + | c[i+1]+=c[i]/10; | ||
| + | c[i]%=10; | ||
| + | } | ||
| + | if(c[n+m+1]!=0) n++; | ||
| + | for(int i=n+m;i>=0;i--) printf("%d",c[i]); | ||
| + | return 0; | ||
| + | } | ||
| + | </code> | ||
| + | </hidden> | ||
| + | |||
| + | ==== 例 3 ==== | ||
| + | |||
| + | === 题意 === | ||
| + | |||
| + | [[https://www.luogu.com.cn/problem/P4245|P4245 【模板】任意模数多项式乘法]] | ||
| + | |||
| + | 给定 $2$ 个多项式 $F(x),G(x)$,请求出 $F(x)*G(x)$,系数对 $p$ 取模,且不保证 $p$ 可以分解成 $p=a\cdot 2^k+1$ 的形式。$1\le n,m\le 10^5,0\le a_i,b_i\le 10^9,2\le p\le 10^9+9$。 | ||
| + | |||
| + | === 题解 === | ||
| + | |||
| + | 若不对 $p$ 取模,则系数最大值可达到 $np^2=10^{23}$,考虑使用 NTT 求解,选取 $3$ 个模数 $p_1,p_2,p_3$,使得 $p_1p_2p_3>np^2$,分别求出这 $3$ 个模下的系数 $x$,然后通过中国剩余定理合并答案,最后再对 $p$ 取模得到最终答案。过程中注意防止 long long 溢出。 | ||
| + | |||
| + | === 评价 === | ||
| + | |||
| + | MTT 模板题 | ||
| + | |||
| + | === 代码 === | ||
| + | |||
| + | <hidden> | ||
| + | <code cpp> | ||
| + | #include<bits/stdc++.h> | ||
| + | using namespace std; | ||
| + | const int N=1e5+5; | ||
| + | typedef long long ll; | ||
| + | int rev[N<<2]; | ||
| + | ll n,m; | ||
| + | ll a[N<<2],b[N<<2],c[N<<2]; | ||
| + | ll a1[N<<2],b1[N<<2]; | ||
| + | ll x[4][N<<2]; | ||
| + | const ll p[4]={0,998244353,2281701377,469762049},G=3;// 3 个都有原根 3 的大模数 | ||
| + | const ll p_12=p[1]*p[2]; | ||
| + | ll fastpow(ll x,ll y,ll mod){ | ||
| + | ll ret=1; | ||
| + | for(;y;y>>=1,x=x*x%mod) | ||
| + | if(y&1) ret=ret*x%mod; | ||
| + | return ret; | ||
| + | } | ||
| + | const ll inv1=fastpow(p[1],p[2]-2,p[2]),inv2=fastpow(p_12%p[3],p[3]-2,p[3]); | ||
| + | void NTT(ll *y,int len,int on,ll P){ | ||
| + | ll Gi=fastpow(G,P-2,P); | ||
| + | for(int i=0;i<len;i++) | ||
| + | if(i<rev[i]) | ||
| + | swap(y[i],y[rev[i]]); | ||
| + | for(int mid=1;mid<len;mid<<=1){ | ||
| + | ll wn=fastpow(on==1?G:Gi,(P-1)/(mid<<1),P); | ||
| + | for(int j=0;j<len;j+=(mid<<1)){ | ||
| + | ll w=1; | ||
| + | for(int k=0;k<mid;k++,w=w*wn%P){ | ||
| + | ll u=y[j+k],t=w*y[j+k+mid]%P; | ||
| + | y[j+k]=(u+t)%P; | ||
| + | y[j+k+mid]=(u-t+P)%P; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | void calc(ll *A,ll *B,int len,ll P,ll *ans){// 计算某个模数 P 下的 NTT | ||
| + | NTT(A,len,1,P); | ||
| + | NTT(B,len,1,P); | ||
| + | for(int i=0;i<len;i++) A[i]=A[i]*B[i]%P; | ||
| + | NTT(A,len,-1,P); | ||
| + | ll inv=fastpow(len,P-2,P); | ||
| + | for(int i=0;i<=n+m;i++) ans[i]=A[i]*inv%P; | ||
| + | } | ||
| + | void MTT(ll *A,ll *B,int len,ll P,ll *C){// 进行 3 次不同模数下的 NTT,然后通过中国剩余定理合并得到答案 | ||
| + | for(int i=1;i<=3;i++){ | ||
| + | memcpy(a1,a,sizeof(a1)); | ||
| + | memcpy(b1,b,sizeof(b1)); | ||
| + | calc(a1,b1,len,p[i],x[i]); | ||
| + | } | ||
| + | for(int i=0;i<=n+m;i++){ | ||
| + | ll ret=(x[2][i]-x[1][i]+p[2])%p[2]*inv1%p[2]*p[1]+x[1][i]; | ||
| + | C[i]=((x[3][i]-ret%p[3]+p[3])%p[3]*inv2%p[3]*(p_12%P)%P+ret)%P; | ||
| + | } | ||
| + | } | ||
| + | int main(){ | ||
| + | ll P; | ||
| + | scanf("%lld%lld%lld",&n,&m,&P); | ||
| + | for(int i=0;i<=n;i++) scanf("%lld",&a[i]),a[i]%=P; | ||
| + | for(int i=0;i<=m;i++) scanf("%lld",&b[i]),b[i]%=P; | ||
| + | int len=1,l=0; | ||
| + | while(len<=n+m) len<<=1,l++; | ||
| + | for(int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1)); | ||
| + | MTT(a,b,len,P,c); | ||
| + | for(int i=0;i<=n+m;i++) | ||
| + | printf("%lld ",c[i]); | ||
| return 0; | return 0; | ||
| } | } | ||
| </code> | </code> | ||
| </hidden> | </hidden> | ||