0. 楔子

我们知道,\(\mathtt{FFT}\) 是用复数运算的,当我们需要取模且数据范围较大的时候就没有办法了,比如 这道题

如果能减小数据范围,我们就可以先算再模,于是,拆系数 \(\mathtt{FFT}\) 就闪亮登场了!

1. 正文

首先计算一下本题的数据范围:\(10^9\times 10^9\times 10^5=10^{23}\)(有 \(a_{x+y}\leftarrow a_xa_y\),而且这样的 \(x,y\) 的组合是 \(n\) 级别的)。

\(\text{base}\) 为一个 \(\sqrt p\) 级别的数,那么类似除法的定义,可以将 \(F(x)\) 分解成 \(\text{base}\cdot A(x)+B(x)\),这样拆出来的函数的系数是 \(<\text{base}\) 的。

\[F*G=(\text{base}\cdot A+B)*(\text{base}\cdot C+D) \]

\[=\text{base}^2\cdot A*C+\text{base}\cdot (A*D+B*C)+B*D \]

这样数据范围就是 \(10^{14}\) 左右,可以用 \(\text{long long}\) 存储。但是这样要做 \(7\)\(\mathtt{FFT}\)(插值 \(4\) 次,转换 \(3\) 次)。为啥不能将三者的点值表达式直接相加呢?显然第一个与第二个点值表达式乘上 \(\rm base\) 就直接爆了,而点值表达式又不能取模,所以只有转回去,取模再相乘、相加。

我们知道,在进行朴素 \(\mathtt{FFT}\) 时虚部并未被使用,我们可以利用这个空间。

考虑计算 \(A,B,C,D\) 的点值表示。令(注意下文将 \(f(\omega_n^k)\) 简写成 \(f(k)\)

\[f(k)=A(k)+i\times B(k) \]

\[g(k)=A(k)-i\times B(k) \]

\[h(k)=C(k)+i\times D(k) \]

我们发现 \(f(k),g(n-k)\) 是共轭的(\(n\)\(n\) 补全的 \(2\) 的幂):

\[\begin{align}f(k)&=A(k)+i\times B(k) \\&=\sum_{j=0}^{n-1}a_j(\omega_n^k)^j+i\times \sum_{j=0}^{n-1}b_j(\omega_n^k)^j\\&=\sum_{j=0}^{n-1}(a_j+i\times b_j)(\omega_n^k)^j\\&=\sum_{j=0}^{n-1}(a_j+i\times b_j)\left(\cos\left(\frac{2\pi kj}{n}\right)+i\sin \left(\frac{2\pi kj}{n}\right)\right)\\&=\sum_{j=0}^{n-1}\left(a_j\times \cos\left(\frac{2\pi kj}{n}\right)-b_j\times \sin \left(\frac{2\pi kj}{n}\right)\right)+i\left(b_j\times \cos\left(\frac{2\pi kj}{n}\right)+a_j\times \sin \left(\frac{2\pi kj}{n}\right)\right)\end{align} \]

\[\begin{align}g(n-k)&=A(n-k)-i\times B(n-k)\\&=\sum_{j=0}^{n-1}a_j(\omega_n^{-k})^j-i\times \sum_{j=0}^{n-1}b_j(\omega_n^{-k})^j\\&=\sum_{j=0}^{n-1}(a_j-i\times b_j)(\omega_n^{-k})^j\\&=\sum_{j=0}^{n-1}(a_j-i\times b_j)\left(\cos\left(\frac{2\pi kj}{n}\right)-i\sin \left(\frac{2\pi kj}{n}\right)\right)\\&=\sum_{j=0}^{n-1}\left(a_j\times \cos\left(\frac{2\pi kj}{n}\right)-b_j\times \sin \left(\frac{2\pi kj}{n}\right)\right)-i\left(b_j\times \cos\left(\frac{2\pi kj}{n}\right)+a_j\times \sin \left(\frac{2\pi kj}{n}\right)\right)\end{align} \]

需要注意的是,\(f(k),g(n-k)\) 共轭也就代表着 \(f(n)\)\(g(0)\) 配对,所以 \(g(0)\) 需要特殊计算。

综上,计算 \(f,g,h\) 的点值表达式只用 \(2\)\(\mathtt{FFT}\).

\(p=f*h,q=g*h\). 所以

\[p=A*C-B*D+i(A*D+B*C) \]

\[q=A*C+B*D+i(A*D-B*C) \]

\(p,q\) 转回系数多项式需要 \(2\)\(\mathtt{FFT}\),将 \(p,q\) 对应项相加即可解出 \(A*C,A*D\),从而都解出来。

总共需要 \(4\)\(\mathtt{FFT}\).

2. 代码实现

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' || s<'0')
		f |= (s=='-');
	while(s>='0' && s<='9')
		x = (x<<1)+(x<<3)+(s^48),
		s = getchar();
	return f?-x:x;
}

template <class T>
inline void write(T x) {
	static int writ[50],tp=0;
	if(x<0) putchar('-'),x=-x;
	do writ[++tp] = x-x/10*10, x/=10; while(x);
	while(tp) putchar(writ[tp--]^48);
}

#include <cmath>
#include <iostream>
using namespace std;
typedef long long ll;

const int maxn = 4e5+5;
const double PI = acos(-1.0);
const ll bas = (1<<15), bas2 = bas*bas;

struct cp {
	double x,y;
	cp() {}
	cp(const double X,const double Y):x(X),y(Y) {}
	
	cp operator + (const cp& t) { return cp(x+t.x,y+t.y); }
	cp operator - (const cp& t) { return cp(x-t.x,y-t.y); }
	cp operator * (const cp& t) { 
		return cp(x*t.x-y*t.y,x*t.y+y*t.x); 
	}
} f[maxn],g[maxn],h[maxn],w[2][maxn],tmp;
int n,m,mod,lim=1,bit;
int rev[maxn]; 

void preWork() {
	while(lim<n+m-1) lim<<=1,++bit;
	for(int i=0;i<lim;++i) {
		rev[i] = (rev[i>>1]>>1)|((i&1)<<bit-1);
		w[1][i] = cp(cos(PI*2*i/lim),sin(PI*2*i/lim));
		w[0][i] = cp(w[1][i].x,-w[1][i].y);
	}
}

void FFT(cp* f,bool opt=1) {
	for(int i=0;i<lim;++i)
		if(i<rev[i]) swap(f[i],f[rev[i]]);
	for(int mid=1;mid<lim;mid<<=1) 
		for(int i=0;i<lim;i+=(mid<<1))
			for(int j=0;j<mid;++j) {
				tmp = w[opt][lim/mid/2*j]*f[i+j+mid];
				f[i+j+mid] = f[i+j]-tmp;
				f[i+j] = f[i+j]+tmp;
			}
}

int main() {
	int val;
	n=read(9)+1,m=read(9)+1,mod=read(9);
	preWork();
	for(int i=0;i<n;++i)
		val = read(9), f[i] = cp(val>>15,val&32767);
	for(int i=0;i<m;++i)
		val = read(9), h[i] = cp(val>>15,val&32767);
	FFT(f),FFT(h);
	g[0] = cp(f[0].x,-f[0].y);
	for(int i=1;i<lim;++i)
		g[i] = cp(f[lim-i].x,-f[lim-i].y);
	for(int i=0;i<lim;++i) {
		h[i].x /= lim, h[i].y /= lim; // 只用除一次~
		f[i] = f[i]*h[i];
		g[i] = g[i]*h[i];
	}
	FFT(f,0),FFT(g,0);
	for(int i=0;i<n+m-1;++i) {
		int AC = ((ll)((f[i].x+g[i].x)/2+0.5))%mod;
		int AD = ((ll)((f[i].y+g[i].y)/2+0.5))%mod;
		int BD = ((ll)round(g[i].x-AC)%mod+mod)%mod;
		int BC = ((ll)round(f[i].y-AD)%mod+mod)%mod;
		print((bas2*AC+bas*(AD+BC)+BD)%mod,' ');
	}
	return 0;
}
posted on 2021-02-16 20:23  Oxide  阅读(121)  评论(0编辑  收藏  举报