zoukankan      html  css  js  c++  java
  • 多项式全家桶——Part.1 多项式加减乘

    多项式全家桶它lei了。

    好吧,最近发现自己的多项式芝士严重匮乏,发现只会FFT和NTT,而且还有点生疏。
    那既然没事干,那就来吃吃全家桶来补充芝士储备。

    在这里插入图片描述

    多项式

    多项式是一个神奇的东东。
    它长这样:(sum_{i=0}^{n-1} a_ix^i)
    好的,讲完了。

    多项式加法、减法

    由于多项式长这样:(sum a_ix^i)
    那么假设这两个多项式相加、减:(sum a_ix^i、sum b_ix^i)
    那么结论就是:(sum (a_ipm b_i)x^i)

    多项式乘法

    这玩意长这样:((1+a_1x+a_2x^2+…+a_{n-1}x^{n-1})(1+b_1x+b_2x^2+…+b_{m-1}x^{m-1}))
    两个多项式相乘就叫做多项式乘法。
    具体做法有三种,FFT、NTT 和 MTT
    超强wd的博客有讲FFT

    这玩意我们直接暴力求是(O(n^2))的。
    然后就出现了两个低级算法:DFT和IDFT

    DFT、IDFT

    DFT叫做离散傅里叶变换,IDFT则是离散傅里叶逆变换。

    DFT就是将系数表示法转成点值表示法。
    IDFT则是反过来。

    有什么好处呢?转成点值后:
    (A=(x_1,A(x_1))(x_2,A(x_2))(x_3,A(x_3))……(x_{n-1},A(x_{n-1})))
    (B=(x_1,B(x_1))(x_2,B(x_2))(x_3,B(x_3))……(x_{n-1},B(x_{n-1})))

    那么乘起来就是:
    (A*B=(x_1,A(x_1)*B(x_1))(x_2,A(x_2)*B(x_2))(x_3,A(x_3)*B(x_3))……(x_{n-1},A(x_{n-1})*B(x_{n-1})))

    这样我们可以在(O(n))时间内做出。

    然鹅朴素的DFT和IDFT还是(O(n^2))的,那么超级算法FFT就是用来优化之的。

    FFT

    流程图:
    在这里插入图片描述

    单位根的性质Copy我那个被吃掉的博客:

    ———————————————————————————————————
    在这里插入图片描述
    1、(omega_n^n=omega_n^0=1)
    2、(omega_n^xomega_n^y=omega_n^{x+y}=omega_n^{(x+y)mod n})
    因为两个相乘就相当于两个相加(显然),然后就相当于旋转了(x+y)次。如果mod一下,那么就相当于转了很多圈回到了原来的一个值。
    3、(omega_n^x=omega_n^{n+x})
    证明与上面的一样。
    4、群的性质:满足(omega_n^x<>omega_n^y)当且仅当x mod n<>y mod n
    5、消去引理:(omega_{dn}^{dx}=omega_n^x)
    6、折半引理:(({omega_n^i})^2=(omega_n^{i+ frac n2 })^2=omega_n^{2i} (当2|n时))
    证明:
    (ecause({omega_n^i})^2=omega_n^{2i})
    (ecauseomega_n^{2i}=omega_n^{2i+n}=(omega_n^{i+ frac n2 })^2)
    ( herefore({omega_n^i})^2=(omega_n^{i+ frac n2 })^2)
    事实上:(omega_n^i=-omega_n^{i+ frac n2 })
    很好理解恩?
    7、求和引理: 对于任意正整数n和非负整数k,且n不是k的倍数时,满足:(sum_{i=0}^{n-1}(omega^k_n)^i=0)
    证明:
    运用等比数列:

    [sum_{i=0}^{n-1}(omega^k_n)^i=frac{1-(omega_n^k)^n}{1-omega_n^k}=frac{1-(omega_n^n)^k}{1-omega_n^k}=frac{1-1^k}{1-omega_n^k}=0 ]

    8、不知道什么引理或定理:
    (omega_n^{k+frac n2}=-omega_n^k)

    ———————————————————————————————————

    现在我们知道了单位根的性质了。
    然后我们康康FFT怎么来用单位根的性质来加速。

    [A(x)=sum_{i=0}^{n-1}a_i*x^i=a_0*x^0+a_1*x^1+a_2*x^2+……+a_{n-1}*x^{n-1} ]

    我们把这个玩意按照下标奇偶性来分个类。

    [=(a_0*x^0+a_2*x^2+……+a_{n-2}*x^{n-2})+(a_1*x^1+a_3*x^3+……+a_{n-1}*x^{n-1}) \=(a_0*x^0+a_2*x^2+……+a_{n-2}*x^{n-2})+x*(a_1*x^0+a_3*x^2+……+a_{n-1}*x^{n-2})]

    [A1(x)=a_0*x^0+a_2*x+a_4*x^2+……+a_{n-2}*x^frac{n-2}2\A2(x)=a_1*x^0+a_3*x+a_5*x^2……+a_{n-1}*x^frac{n-2}2 ]

    [A(x)=A1(x^2)+x*A2(x^2) ]

    接下来我们开始利用单位根了!
    在这里插入图片描述

    (k<=frac n 2),然后把(omega_n^k)当做(x)代入得到:

    [A(omega_n^k)=A1((omega_n^k)^2)+omega_n^k*A2((omega_n^k)^2) ]

    根据折半引理:

    [A(omega_n^k)=A1(omega_n^{2k})+omega_n^k*A2(omega_n^{2k}) ]

    根据消去引理:

    [A(omega_n^k)=A1(omega_frac n2^k)+omega_n^k*A2(omega_frac n2^k) ]

    然后再把(omega_n^{k+frac n 2})代入得到

    [A(omega_n^{k+frac n 2}) =A1((omega_n^{k+frac n 2})^2)+omega_n^{k+frac n 2}*A2((omega_n^{k+frac n 2})^2) \=A1(omega_n^{2k+n})-omega_n^k*A2(omega_n^{2k+n}) \=A1(omega_n^{2k})-omega_n^k*A2(omega_n^{2k}) \=A1(omega_frac n2^k)-omega_n^k*A2(omega_frac n2^k)]

    继而发现,上面两坨玩意儿只有一个符号变了。
    意味着,求出(A1(omega_frac n2^k))(A2(omega_frac n2^k))的答案,即可求出(A(omega_n^k))(A(omega_n^{k+frac n 2}))

    于是我们就可以很开心地分治了。
    由于分治的常数极大,所以我这里就不贴代码了。
    下面有个神奇的优化可以完善之。

    IFFT

    前面加了个I的东东都是原来的东东的逆运算。
    所以IFFT就是将点值转为插值的算法。

    哦,点值转插值,这个我会。不就是拉格朗日插值吗?
    那你可去试试

    反正这玩意可以有多种方法来表示,什么矩阵,什么奇怪的推柿子。
    然鹅我都不肥。

    那就记住结论闯天下了。

    • fft过程中乘上共轭复数,然后做完后再除以n就是插值了。

    证明别找我。

    迭代大法

    在这里插入图片描述
    于是我们发现:
    每个下标的二进制形式反过来就是它们最后在序列中的位置。

    蝴蝶变换

    我不知道。

    于是直接rush。

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    using namespace std;
    const double Pi=3.141592653589323846;
    const int maxn=5e6;
    
    int n,m,up,dep;
    int rec[maxn];
    
    struct node{
    	double x,y;
    }a[maxn],b[maxn];
    
    node cheng(node a,node b)
    {
    	node c;
    	c.x=a.x*b.x-a.y*b.y;
    	c.y=a.x*b.y+a.y*b.x;
    	return c;
    }
    
    node jia(node a,node b)
    {
    	node c;
    	c.x=a.x+b.x;
    	c.y=a.y+b.y;
    	return c;
    }
    
    node jian(node a,node b)
    {
    	node c;
    	c.x=a.x-b.x;
    	c.y=a.y-b.y;
    	return c;
    }
    
    void FFT(node *a,int n,int inv)
    {
    	if (n==1) return;
    	int mid=n/2;
    	for (int i=0;i<n;i++)
    	{
    		rec[i]=(rec[i>>1]>>1)|((i&1)<<(dep-1));
    		if (i<rec[i]) swap(a[i],a[rec[i]]);
    	}
    	for (int len=1;len<=n;len<<=1)
    	{
    		int mid=len/2;
    		node w,wn={cos(Pi/mid),inv*sin(Pi/mid)};
    		for (int j=0;j<n;j+=len)
    		{
    			w={1,0};
    			for (int i=0;i<mid;i++)
    			{
    				node q=cheng(w,a[j+mid+i]),p=a[j+i];
    				a[j+mid+i]=jian(p,q);
    				a[j+i]=jia(p,q);
    				w=cheng(w,wn);
    			}
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for (int i=0;i<=n;i++)
    	{
    		int xx;
    		scanf("%d",&xx);
    		a[i].x=xx;a[i].y=0;
    	} 
    	for (int i=0;i<=m;i++)
    	{
    		int xx;
    		scanf("%d",&xx);
    		b[i].x=xx;b[i].y=0;
    	}
    	up=1;dep=0;
    	while (up<=n+m) up=up*2,dep++;
    	FFT(a,up,1);
    	FFT(b,up,1);
    	for (int i=0;i<up;i++)
    	{
    		a[i]=cheng(a[i],b[i]);
    	}
    	FFT(a,up,-1);
    	for (int i=0;i<=n+m;i++)
    	{
    		printf("%d ",(int)(a[i].x/up+0.5));
    	}
    }
    

    学习资料:
    百度百科
    https://www.cnblogs.com/Chandery/p/11332777.html
    https://blog.csdn.net/YY_Tina/article/details/88361459
    https://blog.csdn.net/enjoy_pascal/article/details/81478582
    https://www.cnblogs.com/zwfymqz/p/8244902.html#_label4

    NTT

    直接看这个了,自我感觉写得还阔以

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    using namespace std;
    const long long mo=1004535809;
    const int maxn=5e6;
    
    int n,m,up,dep;
    int rec[maxn],w[maxn];
    long long a[maxn],b[maxn];
    
    long long qsm(long long a,long long b)
    {
    	long long t=1;
    	long long y=a;
    	while (b>0)
    	{
    		if ((b&1)==1) t=t*y%mo;
    		y=y*y%mo;
    		b/=2;
    	}
    	return t;
    }
    
    void FFT(long long a[],int n,int inv)
    {
    	if (n==1) return;
    	int mid=n/2;
    	for (int i=0;i<n;i++)
    	{
    		rec[i]=(rec[i>>1]>>1)|((i&1)<<(dep-1));
    		if (i<rec[i]) swap(a[i],a[rec[i]]);
    	}
    	for (int len=2;len<=n;len<<=1)
    	{
    		int mid=len/2;
    		for (int j=0;j<mid;j++)
    		{
    			for (int k=j;k<n;k+=len)
    			{
    				int p,q;
    				q=w[j*(n/len)]*a[k+mid]%mo,p=a[k];
    				a[k+mid]=(p-q+mo)%mo;
    				a[k]=(p+q+mo)%mo;
    			}
    		}
    	}
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for (int i=0;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    	} 
    	for (int i=0;i<=m;i++)
    	{
    		scanf("%d",&b[i]);
    	}
    	n++;m++;
    	up=1;dep=0;
    	while (up<=n+m) up=up*2,dep++;
    	w[0]=1;long long rev=qsm(3,(mo-1)/up);
    	for (int i=1;i<=up;i++)
    	{
    		w[i]=w[i-1]*rev%mo;
    	}
    	FFT(a,up,1);
    	FFT(b,up,1);
    	for (int i=0;i<up;i++)
    	{
    		a[i]=(a[i]*b[i])%mo;
    	}
    	for (int i=0;i<=up/2;i++)
    	{
    		swap(w[i],w[up-i]);
    	}
    	FFT(a,up,-1);
    	for (int i=0;i<=n+m-2;i++)
    	{
    		printf("%d ",a[i]*qsm(up,mo-2)%mo);
    	}
    }
    

    MTT

    这什么毒瘤东东。
    放放,有时间再学吧
    ColdChair大爷

  • 相关阅读:
    LINNX联网配置文件
    linux文件系统配置文件
    linux引导和登录/注销配置文件
    LINUX访问文件配置
    LINUX配置文件介绍
    tcpdump的表达式介绍
    tcpdump命令介绍
    DNS客户端配置文件/etc/resolv.conf
    tcpdump概述
    LINUX普通猫的拔号工具介绍
  • 原文地址:https://www.cnblogs.com/RainbowCrown/p/13431786.html
Copyright © 2011-2022 走看看