zoukankan      html  css  js  c++  java
  • 快速数论变换(NTT)

    前置芝士:快速傅立叶变换(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(0le 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)

    性质四(sumlimits_{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 e phi(p))使得(g^kequiv 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;
    }
    
  • 相关阅读:
    python -- twisted初探
    python -- redis连接与使用
    redis使用
    python -- 异步编程
    python
    python
    福大软工 · 最终作业
    福大软工 · 第十二次作业
    Beta 冲刺(7/7)
    Beta 冲刺(6/7)
  • 原文地址:https://www.cnblogs.com/knife-rose/p/12038584.html
Copyright © 2011-2022 走看看