【模板】多项式乘法(FFT/NTT)

题解:https://www.luogu.org/problemnew/show/P3803

题解:直接做是n^2的。可以用FFT优化。首先要知道一些知识。

一.单位复数根

定义:w_n^k=cosk*\frac{2\pi }{n}+i*sink*\frac{2\pi}{n}

表示单位复数根,相当于将复平面的单位圆等分成n份(n一般是2的多少次幂),从实数轴逆时针旋转对应的坐标。

性质:w_{2n}^{2k}=w_n^k,w_n^{k+\frac{n}{2}}=-w_n^k。还是很容易推出来的。

二.点值表示法

一个n次多项式可以用n个坐标来代替。我们用单位复数根来当做坐标,要求出函数值。设:

A(x)=a_0+a_1x+a_2x^2+a_3x^3+...+a_{n-1}x^{n-1}

奇偶分类:

A_1(x)=a_0+a_2x^+a_4x^2+...+a_{n-2}x^{\frac{n}{2}-1}

A_2(x)=a_1+a_3x+a_5x^2+...+a_{n-1}x^{\frac{n}{2}-1}

A(x)=A_1(x^2)+x*A_2(x^2)

将w(k,n)代入看、k<n/2

A(w_n^k)=A_1(w_n^{2k})+w_n^k*A_2(w_n^{2k})

将w(k+n/2,n)代入

A(w_n^{k+\frac{n}{2}})=A_1(w_n^{2k})-w_n^k*A_2(w_n^{2k})

注意到上面两个式子只有一个符号不同,所以通过枚举第一个式子可以O(1)的求出另一个,这样规模就减半了。

三.逆变换

设:c_k=\sum_{i=0}^{n-1}y_i(w_n^{-k})^ia_k=\frac{c_k}{n}

四.迭代实现

通过观察性质,反转后的序列就是原序列的下标的二进制反转。我们用数位dp的思想。r[i]表示将i翻转后的下标。例如11001->10011。其实就是将最右边的1移到最高位,剩下的1100已经在之前做过了,直接将两部分合并就行了。

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const double pi=acos(-1);
struct cp{
	double r,i;
	cp(){}
	cp(double x,double y){
		r=x;i=y;
	}
	cp operator +(const cp &a) const {
		return cp(r+a.r,i+a.i);
	} 
	cp operator - (const cp &a) const{
		return cp(r-a.r,i-a.i);
	}
	cp operator * (const cp &a)const {
		return cp(r*a.r-i*a.i,r*a.i+i*a.r);
	}
}a[N<<2],b[N<<2];
int R[N<<2];//反码 
int n,m,L;

inline void fft(cp *a,int len,int op){
	
	for(int i=0;i<len;i++) {
		if(i<R[i]) swap(a[ i ],a[ R[i] ]);
	}
	for(int i=2;i<=len;i<<=1){// 
		cp wn=cp(cos(2*pi/i), op*sin(2*pi/i));//单位复数根 
		for(int j=0;j<len;j+=i){
			cp w=cp(1.0,0.0);
			for(int k=j;k<j+(i/2);k++,w=w*wn){
				cp u=a[k];
				cp v=w*a[k+i/2]; 
				a[k]=u+v;
				a[k+i/2]=u-v;
			}
			
		}
		
		
	}
	if(op==-1) for(int i=0;i<len;i++) a[i].r/=len;
}


int main(){
//	cp a=cp(1.0,2.0);
//	cp b=cp(1.0,2.0);
//	cp c=a*b;
	scanf("%d%d",&n,&m);
	for(int i=0;i<=n;i++) scanf("%lf",&a[i].r);
	for(int i=0;i<=m;i++) scanf("%lf",&b[i].r);
	
	// 初始化 
	m+=n;n=1;L=0;
	for(;n<=m;n<<=1)L++;
    for(int i=0;i<n;i++) R[i]=(R[i>>1]>>1)|(i&1)<<(L-1);
  
    fft(a,n,1);fft(b,n,1);
    for(int i=0;i<n;i++) a[i]=a[i]*b[i];
    fft(a,n,-1);

	for(int i=0;i<=m;i++) printf("%d ",(int)(a[i].r+0.5));//四舍五入 
		
	return 0;
} 

/*
3 4
1 2 3 4
1 2 3 4 5 

//0 4 2 6 1 5 3 7 R[i] n=8 

*/

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值