zoukankan      html  css  js  c++  java
  • NTT学习笔记

    FFT NTT TNT 实现爆破的伟大之路。

    FFT容易爆精度 这大家都知道所以在卷积之后要+0.5然后int强制转换。

    有的时候题目中可以取模 但是我们FFT中带入的x是浮点型的 无法支持取模操作。

    我们知道 取模是数论的内容。 那么 ~~老爹说过要用数论打败数论~~

    代替单位复根的就是原根了。所谓原根就是设m是正整数,a是整数,若a模m的阶等于φ(m),则称a为模m的一个原根。

    什么叫阶 a和模数m互质 使得 $a^dequiv 1(mod m)$成立的最小正整数d叫做a对模m的阶.

    那么性质,a是阶的话对于正整数$1leq pleq m-1$有$a^p(mod p)$两两不相同 且当p为m-1时值为1.这牵扯到完全剩余系的问题了...

    原根的性质 对于m是质数的时候 若g是m的原根 那么存在$g^{m-1}$=1(modp) ~~当然同余号太难打这里写等号~~

    可以$w_n$把$g^{(p-1)/n}$看成是等价的

    所以说这种质数必须是NTT质数(费马质数),即(P-1)有超过序列长度的2的正整数幂因子的质数,如一般这种质数都是998244353,1004535809,469762049等。

    不是这种质数怎么办?找几个找乘积大于p^2*n的费马质数做,用中国剩余定理合并就好了。

    为什么998244353是NTT的常用模数呢?NTT到底和单位根的本质上的联系是什么呢?

    拿起计算器可以算一下 $998244353-1$ 之后大约是一个 $119 cdot 2^{23}=998244352$. 很神奇对吧 这又意味着什么呢。

    考虑我们的单位根 一开始我们选取2的若干次幂的底数x 每个东西是什么$e^{frac{2π}{2^w}}$这个东西就是我们的自变量了。

    为什么是这个我就不再赘述 一些消去原理和折半原理 以及 单位根的特性然后实现FFT

    考虑有模数的限制的话我们就不能再选取单位根了 因为那是虚数和小数的夹杂。

    先来点简单的如模数就是$998244353$这个NTT常用模数的时候这个模数我简记为mod,我们选取的x 首先 数量要大于n+m+1。

    然后我们想要折半降低复杂度的话,原根的一个特点 设g为$mod$的原根 那么存在$g^{mod-1}$%mod=1;

    且 原根的完全剩余系为[1,mod-1] 当然关于这方面的知识我也不太清楚 不能再口胡下去了。

    我们的x的选取 即是$g^{frac{mod-1}{2^k}}$ 这样我们考虑一下折半原理的使用

    在我们分治之后 设P(x) 表示在当前这层我们要计算的一个值 k是当前的次数 (因为毕竟是一个递归的过程

    p(x)=$a_0+a_1x+a_2x^2+...a_kx^{k-1}$ 这样的话我们设两个式子

    $A_0$(x)=$a_0+a_2x+a_4x^2+...a_{k-2}x^{frac{k}{2}-1}$

    $A_1$(x)=$a_1+a_3x+a_5x^2+...a_{k-1}x^{frac{k}{2}-1}$

    那么p(x)=$A_0(x^2)$+$xA_1(x^2)$ 我们先前有了 一个折半原理 是这样的 先说单位根吧。

    $w_n^k=e^{frac{2kπi}{n}}$ 我们给个平方 $(w_n^k)^2=(e^{frac{2kπi}{n}})^2=w_{frac{n}{2}}^k$ 挺显然的说实话。

    然后之所以能折半是因为 考虑一下当$kleqfrac{n}{2}-1$ $(w_n^{k+frac{n}{2}})^2=(w_n^kcdot w_n^n)^2$ 由单位根的定义可知$w_n^n$=1;

    那么我们得到的那个等式 $(w_n^{k+frac{n}{2}})^2=(w_n^k)^2$ 而$w_n^k$的平方也是$(w_n^k)^2$

    所以说我们带入$x^2$的时候 有一半是不用计算的 他们的平方是相等的 且我们计算出来他们的平方然后分别处理他们即可。这样就可以完成折半了。

    接下来引导我们的原根上面 刚才我们提到了我们的x是$g^{frac{k*(mod-1)}{2^n}}$

    考虑一下 还是考虑较少的一半 $kleqfrac{n}{2}-1$的时候 首先先求一下$(g^{frac{k(mod-1)}{n}})^2=?$

    由于本人才疏学浅以下皆为口胡.

    接下来考虑另外一半 $g^{frac{(k+n)cdot{(mod-1)}}{n}}$=$g^{frac{kcdot(mod-1)}{n}}$ 证明不难。

    所以就是这样就做好了折半也是可以折了 然后 没了 就这样搞 至于 为什么是998244353这个模数为常用模数

    考虑我们折半的时候 每折一次 我们都会变成2的若干次方998244352这个数字刚好能折23次 也就是说可以胜任8388608这么大的多项式的卷积

    考虑不能这么搞怎么办 将模数分解成若干$xcdot{2^k}+1$的质数的乘积 最后利用中国剩余定理进行合并即可。

    的确毒瘤我还需要好好的思考。

    最后再点一下插值的计算。

    插值:根据已知数据点(条件),来预测未知数据点值得方法。假如你有n个已知条件,就可以求一个n-1次的插值函数P(x),使得P(x)接近未知原函数f(x),并由插值函数预测出你需要的未知点值。更简单的定义:根据点值求函数.

    我们 还是够早我们 原矩阵的逆矩阵再套用FFT计算即可.注意 带入的x变成逆元。

    给出一道模板题目吧。[多项式乘法求逆](https://www.luogu.com.cn/problem/P4238)

    这年头NTT模板图模数给的不太正常可以用FFT来写 这里还是来一发标准的NTT吧。

    求$F(x)cdot G(x)equiv 1(mod x^n)$ 然后正确的推导过程如下:

    设$F(x)cdot H(x)equiv 1(mod x^{frac{n}{2}}) ag {1}$

    也有$F(x)cdot G(x)equiv 1(mod x^{frac{n}{2}}) ag {2}$

    由(2)-(1) 可以得到 $F(x)(G(x)-H(x))equiv 0(mod x^{frac{n}{2}})$

    所以可以得到$G(x)-H(x)equiv 0(mod x^{frac{n}{2}})$ 关于这个等式的理解是

    由第一个条件可知$F(x)$之中必然含有一个不等于0的常数项 注意这道题的$mod=998244353$.

    这样的话 $G(x)-H(x)$的常数项必然为0或者在对$mod$取模的意义下为0

    我们再来贯穿$G(x)-H(x)$之中最小的$x^r$的幂这个东西乘上$F(x)$常数必然不能被模掉或者被消掉因为$G(x)-H(x)$的常数项为0 所以没有$x^r$这一项所以归纳来思考$G(x)-H(x)equiv 0(mod x^{frac{n}{2}})$

    把刚刚推得的式子平方$(G(x)-H(x))^2equiv 0(mod x^n)$ 再展开$G(x)^2-2G(x)H(x)+H(x)^2equiv 0(mod x^n)$

    等式两边同乘以$F(x)$得到$G(x)-2H(x)+F(x)H(x)^2equiv 0(mod x^n)$

    那么$G(x)equiv 2H(x)-F(x)H(x)^2 (mod x^n)$ 我们发现只需要求H(x)就可以了 成功把问题缩小一半直到缩小到1的时候我们直接求一个逆元即可。

    //#include<bitsstdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<ctime>
    #include<cmath>
    #include<cctype>
    #include<cstdlib>
    #include<queue>
    #include<deque>
    #include<stack>
    #include<vector>
    #include<algorithm>
    #include<utility>
    #include<bitset>
    #include<set>
    #include<map>
    #define ll long long
    #define db double
    #define ld long double
    #define put(x) printf("%d
    ",x)
    #define min(x,y) ((x)>(y)?(y):(x))
    #define max(x,y) ((x)>(y)?(x):(y))
    #define EPS 1e-8
    #define ui unsigned int
    #define pb push_back
    #define RI register int
    using namespace std;
    char buf[1<<15],*fs,*ft;
    inline char getc()
    {
    	return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;
    }
    inline int read()
    {
    	int x=0,f=1;char ch=getc();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();}
    	return x*f;
    }
    const int MAXN=300010,mod=998244353,G=3;
    int n;
    int a[MAXN],b[MAXN],c[MAXN],rev[MAXN];
    inline ll ksm(ll b,ll p)
    {
    	ll cnt=1;
    	while(p)
    	{
    		if(p&1)cnt=cnt*b%mod;
    		b=b*b%mod;
    		p=p>>1;
    	}
    	return cnt;
    }
    inline void NTT(int *a,int n,int f)
    {
    	for(int i=0;i<n;++i)if(i<rev[i])swap(a[i],a[rev[i]]);
    	for(int len=2;len<=n;len=len<<1)//当前的区间长度
    	{
    		int mid=len>>1;
    		int gn=ksm(G,(mod-1)/len);
    		if(f==-1)gn=ksm(gn,mod-2);
    		for(int j=0;j<n;j+=len)
    		{
    			int g=1;
    			for(int i=0;i<mid;++i)
    			{
    				int x=a[j+i],y=(ll)g*a[j+i+mid]%mod;
    				a[j+i]=(x+y)%mod;a[j+i+mid]=(x-y+mod)%mod;
    				g=1ll*g*gn%mod;
    			}
    		}
    	}
    	if(f==1)return;
    	int w=ksm(n,mod-2);
    	for(int i=0;i<n;++i)a[i]=(ll)a[i]*w%mod;
    }
    inline void sol(int n,int *a,int *b)
    {
    	if(n==1){b[0]=ksm(a[0],mod-2);return;}
    	sol((n+1)>>1,a,b);
    	int len=0,s=1;
    	while(s<(n<<1))s=s<<1,++len;//求a的多项式乘b的多项式的点积
    	for(int i=1;i<s;++i)rev[i]=(rev[i>>1]>>1)|((i&1)<<(len-1));
    	for(int i=0;i<n;++i)c[i]=a[i];
    	for(int i=n;i<s;++i)c[i]=0;
    	NTT(c,s,1);NTT(b,s,1);
    	for(int i=0;i<s;++i)b[i]=(2-1ll*c[i]*b[i]%mod+mod)%mod*b[i]%mod;//求点积
    	NTT(b,s,-1);
    	for(int i=n;i<s;++i)b[i]=0;//取一半即可 传到上一层的是当前的一半即可.
    }
    int main()
    {
    	//freopen("1.in","r",stdin);
    	n=read();
    	for(int i=0;i<n;++i)a[i]=read();
    	sol(n,a,b);
    	for(int i=0;i<n;++i)printf("%d ",b[i]);
    	return 0;
    }
    
  • 相关阅读:
    vc 定义返回值为字符串函数方法
    typedef用法(二)
    新版.Net开发必备十大工具【转自www.bitsCN.com】
    大公司面试题
    NET(C#)连接各类数据库集锦
    对对象类型和调用方法属性进行存储以提升反射性能
    数据库连接字符串大全
    C#操作注册表的方法
    上班族解除疲劳
    在Microsoft Visual Studio 2005上安装.net3.0开发环境(含开发环境下
  • 原文地址:https://www.cnblogs.com/chdy/p/12147194.html
Copyright © 2011-2022 走看看