前置芝士:快速傅立叶变换(FFT)
垃圾\(FFT\)
我们已经知道\(FFT\)这个东西他很\(nb\)了,但是仍然存在很多局限性
比如说
\(1.\)要用到复数,所以它很慢
\(2.\)要用到复数,所以是\(double\)类型,有精度误差
\(3.\)要用到复数,不能取模
\(……\)
我们发现就是这个垃圾复数拖了算法后退,要想办法摆脱它(´༎ຶД༎ຶ`)!
原根
聪明的珂学家们发现了一种奇妙的东西:原根
它可以代替复数进行变换,而且具有优良的性质
阶
若\(gcd(a,p)==1\),且\(p>1\)
则对于\(a^x≡1(mod\ p)\)最小的\(x\),我们称为\(a\)的\(p\)的阶,记作\(\delta _p(a)\)
相关定理
定理一:若\(p>1\)且\(gcd(a,p)==1\)且\(a^n≡1(mod\ p)\),则\(\delta _p(a)|n\)
证明:根据定义理解一下吧……
定理二:\(\delta _p(a)|\phi(m)\)
证明:由欧拉定理:\(a^{\phi(p)}≡1(mod\ p)\),所以\(\delta _p(a)≤\phi(p)\),根据定理一得到\(\delta _p(a)|\phi(m)\)
原根
若\(\delta _p(a)=\phi(p)\),则称\(a\)为模\(p\)的一个原根
如\(\delta _9(2)=6=\phi(9)\),称\(2\)为模\(9\)的一个原根
相关定理
定理一: 一个正整数\(p\)存在原根,当且仅当\(p=2,4,p^s,2p^s\),其中\(p\)为奇素数,\(s\)为正整数
定理二: 一个数\(p\)如果存在原根,则原根个数为\(\phi(\phi(p))\)
定理三: 若\(p\)是一个素数,\(g\)是\(p\)的一个原根,那么\(g^i\ mod\ p (1<g<p,0<i<p)\)的结果互不相同
证明咱也不会嘛……\(qwq\)
一些性质
回想一下为什么我们要用单位根作为系数带入多项式?是因为它具有很多优良性质,我们康康这些性质原根有没有!
我们假设一个前提\(p=a*2^k+1\),其中\(k\)很大,沿用\(fft\)的方法,只考虑\(n\)为\(2\)的整次幂的情况
设\(\omega _n^1=g^{\frac{p-1}{n}}\)
首先可以确保\(\omega _n^1\)不再是恶心的复数,因为\(p=a*2^k+1\),所以\(\frac{p-1}{n}\)一定是整数
性质一: \(\omega_ n^k(0\le k \le n-1)\)互不相同
证明:根据原根的定理三可知
性质二:\(\omega _{2n}^{2k} = \omega _n^k\)
证明:\(\omega _{2n}^{2k}=(g^{\frac{2k(p-1)}{2n}})=g^{\frac{k(p-1)}{n}}=\omega _n^k\)
性质三:\(\omega _{n}^{k+\frac{n}{2}} = -\omega _n^k\)
证明:\(\omega _{n}^{k+\frac{n}{2}} = \omega _n^k *\omega _n^{\frac{n}{2}}\)
因为\((\omega _n^{\frac{n}{2}})^2=\omega _n^n=1\)
所以\(\omega _n^{\frac{n}{2}}=1\)或\(-1\)
又因为\(\omega _n^{\frac{n}{2}}与\omega _n^0\)不同
所以\(\omega _n^{\frac{n}{2}}=-1\)
所以\(\omega _{n}^{k+\frac{n}{2}} = -\omega _n^k\)
性质四:\(\sum\limits_{i=0}^{n-1}\omega_ n^i=0\)
证明:和单位根一模一样,懒得打了……
性质五: \(\omega _n^k=\omega _n^{n\%k}\)
证明:\(\omega _n^k=g^{\frac{k(p-1)}{n}}\)
由费马小定理可知,当\(p\)为质数时,\(a^(p-1)\equiv1(mod\ p)\)
所以\(\omega _n^k=g^{\frac{k(p-1)}{n}\% (p-1)}\)
考虑留下的应该是\(\frac{n}{k}\)的小数部分\(*(p-1)\),得到\(\omega _n^k=g^{\frac{k\%n(p-1)}{n}}=\omega _n^{k\%n}\)
我们发现傅立叶变换中需要用到的单位根的性质原根统统满足!那我们还要垃圾单位根干啥!直接上原根啊!
不过也有一定的限制,就在于模数\(p\)必须满足\(p=a*2^k+1\),且不能包含小数
怎么找原根?
暴力枚举(雾
由于一个质数原根有\(\phi(p-1)\)个,最小的原根一般很小
枚举\(g\)和\(k\)复杂度约为\(O(plogp)\)
但是跟具阶的定理二,我们发现如果存在其他\(k\ne \phi(p)\)使得\(g^k\equiv 1(mod\ p)\),那么\(k\)一定是\(\phi(p)\)的约数,所以其实只要枚举\(\phi(p)\)的约数即可
复杂度期望\(O(log^2p)\)
\(code\)
然后我们快来用原根草多项式吧!
#include<bits/stdc++.h> using namespace std; namespace red{ #define int long long #define eps (1e-8) inline int read() { int x=0;char ch,f=1; for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar()); if(ch=='-') f=0,ch=getchar(); while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();} return f?x:-x; } const int N=5e6+10,p=998244353,g=3,gi=332748118; int n,m; int a[N],b[N]; int limit,len; int pos[N]; inline int fast(int x,int k) { int ret=1; while(k) { if(k&1) ret=ret*x%p; x=x*x%p; k>>=1; } return ret; } inline void ntt(int *a,int inv) { for(int i=0;i<limit;++i) if(i<pos[i]) swap(a[i],a[pos[i]]); for(int mid=1;mid<limit;mid<<=1) { int Wn=fast(inv?g:gi,(p-1)/(mid<<1)); for(int r=mid<<1,j=0;j<limit;j+=r) { int w=1; for(int k=0;k<mid;++k,w=w*Wn%p) { int x=a[j+k],y=w*a[j+k+mid]%p; a[j+k]=(x+y)%p; a[j+k+mid]=(x-y)%p; if(a[j+k+mid]<0) a[j+k+mid]+=p; } } } } inline void main() { n=read(),m=read(); for(int i=0;i<=n;++i) a[i]=read(); for(int i=0;i<=m;++i) b[i]=read(); for(limit=1;limit<=n+m;limit<<=1) ++len; for(int i=0;i<limit;++i) pos[i]=(pos[i>>1]>>1)|((i&1)<<(len-1)); ntt(a,1);ntt(b,1); for(int i=0;i<limit;++i) a[i]=a[i]*b[i]%p; ntt(a,0); int inv=fast(limit,p-2); for(int i=0;i<=n+m;++i) printf("%lld ",a[i]*inv%p); } } signed main() { red::main(); return 0; }
来源:https://www.cnblogs.com/knife-rose/p/12038584.html