先要学会FFT[学习笔记]FFT——快速傅里叶变换
FFT会爆精度。而且浮点数相乘常数比取模还大。
然后NTT横空出世了
虽然单位根是个好东西。但是,我们还有更好的东西
我们先选择一个模数,$const\space int\space p=998244353$
设g为p的单位根。这里就是3
那么有:$(\omega_n^1)^n = g^{p-1}=1\space mod \space p$
那么,假设$x=(\omega_n^1)$
其中一个解可以是:$x=g^{\frac{p-1}{n}}$
在模意义之下,我们不妨用$g^{\frac{p-1}{n}}$来代替$(\omega_n^1)$
因为是g原根,所以0~n-1这n个次方取值都不相同,可以求出点值表示。
$\omega_n^{-1}*\omega_n^1=1$
那么$\omega_n^{-1}=(g^{-1})^{\frac{p-1}{n}}$
op的时候,把$g^{-1}$当做底数即可。
其他和FFT相同。
#include<bits/stdc++.h>
#define reg register int
#define il inline
#define numb (ch^'0')
#define int long long
using namespace std;
typedef long long ll;
il void rd(ll &x){
char ch;x=0;bool fl=false;
while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true);
for(x=numb;isdigit(ch=getchar());x=x*10+numb);
(fl==true)&&(x=-x);
}
namespace Miracle{
const int mod=998244353;
const int N=1e6+5;
const int G=3;
const int Gi=332748118;
int qm(int x,int y){
int ret=1;
while(y){
if(y&1) ret=(ll)ret*x%mod;
x=(ll)x*x%mod;
y>>=1;
}
return ret;
}
int n,m;
int a[4*N],b[4*N];
int r[4*N];
void NTT(int *f,int op){
for(reg i=0;i<n;++i){
if(i<r[i]){
swap(f[i],f[r[i]]);
}
}
for(reg p=2;p<=n;p<<=1){
int len=p/2;
ll tmp=qm(op==1?G:Gi,(mod-1)/p);
for(reg k=0;k<n;k+=p){
ll buf=1;
for(reg l=k;l<k+len;++l){
ll tt=(ll)buf*f[l+len]%mod;
f[l+len]=((ll)f[l]-tt);
if(f[l+len]<0) f[l+len]+=mod;
f[l]=((ll)f[l]+tt);
if(f[l]>=mod) f[l]-=mod;
buf=(ll)buf*tmp%mod;
}
}
}
}
void prin(int x){
if(x/10) prin(x/10);
putchar(x%10+'0');
}
int main(){
scanf("%d%d",&n,&m);
for(reg i=0;i<=n;++i){
rd(a[i]);
}
for(reg i=0;i<=m;++i){
rd(b[i]);
}
for(m=n+m,n=1;n<=m;n<<=1);
for(reg i=0;i<n;++i){
r[i]=r[i>>1]>>1|((i&1)?n>>1:0);
}
NTT(a,1);NTT(b,1);
for(reg i=0;i<n;++i) b[i]=(ll)b[i]*a[i]%mod;
NTT(b,-1);
ll inv=qm(n,mod-2);
for(reg i=0;i<=m;++i){
b[i]=(ll)b[i]*inv%mod;
prin(b[i]);putchar(' ');
}
return 0;
}
}
signed main(){
Miracle::main();
return 0;
}
/*
Author: *Miracle*
Date: 2018/11/21 19:01:08
*/
应用大前提:
1.多项式答案的系数不要太大,否则模数乘一下会爆long long,而且必须小于模数
2.多项式的长度不要太长。n<2^23
3.多项式系数必须是正整数!!(废话)
感觉NTT还是一个很好用的东西
常数小,
而且做题的时候,经常会给定模数。FFT一脸懵逼。
如果模数是一个k*2^m+1,并且满足2^m>n(多项式次数),那么可以直接像刚才一样计算。(原根找一下)
如果不是,中国剩余定理合并。
留坑。
二、多项式求逆:
推完式子之后,直接NTT做即可。
注意,
1.每次都要对位数取模,把位数限制在n以内。
2.计算长度为n的逆元的时候,必须算出来的是(n<<1)的多项式(因为H(x)*H(x)*F(x)是长度是n<<1的)
然后再砍掉n~(n<<1)-1的位数部分
可以都转化成点值表示,然后再求G(x)的点值表示。再插值
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=1e5+5; const int mod=998244353; const int GG=3; const int Gi=332748118; int n,m; int F[4*N],G[4*N],A[4*N],B[4*N],C[4*N]; int r[4*N]; int qm(int x,int y){ int ret=1; while(y){ if(y&1) ret=(ll)ret*x%mod; x=(ll)x*x%mod; y>>=1; } return ret; } void NTT(int *f,int op,int n){ for(reg i=0;i<n;++i){ if(i<r[i]) swap(f[i],f[r[i]]); } for(reg p=2;p<=n;p<<=1){ int len=p/2; int tmp=qm(op==1?GG:Gi,(mod-1)/p); for(reg k=0;k<n;k+=p){ int buf=1; for(reg l=k;l<k+len;++l){ int tt=(ll)buf*f[l+len]%mod; f[l+len]=(f[l]-tt+mod)%mod; f[l]=(f[l]+tt)%mod; buf=(ll)buf*tmp%mod; } } } if(op==1) return; int inv=qm(n,mod-2); for(reg i=0;i<n;++i) f[i]=(ll)f[i]*inv%mod; } void wrk(int n,int *a){ if(n==1){a[0]=qm(F[0],mod-2);return;} wrk(n>>1,a); for(reg i=0;i<n;++i) A[i]=F[i];//,B[i]=a[i]; for(reg i=n;i<(n<<1);++i) A[i]=0;//=B[i]=0; for(reg i=0;i<(n<<1);++i){ r[i]=r[i>>1]>>1|((i&1)?n:0); } NTT(A,1,(n<<1)),NTT(a,1,(n<<1)); for(reg i=0;i<(n<<1);++i){ a[i]=(2-(ll)A[i]*a[i]%mod+mod)%mod*a[i]%mod; } NTT(a,-1,(n<<1)); for(reg i=n;i<(n<<1);++i) a[i]=0; } int main(){ scanf("%d",&n); for(reg i=0;i<n;++i){ rd(F[i]);C[i]=F[i]; } int len; for(len=1;len<n;len<<=1); wrk(len,G); for(reg i=0;i<n;++i){ printf("%d ",G[i]); } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2018/11/21 21:49:51 */