MTT 学习笔记

很久以前就听说了这东西,一直没空学。最近重学多项式,就重新搞了一下。

MTT 主要解决的是任意模数(或者说是没有模数)的多项式乘法,可以用于应对专门恶心人的毒瘤题。

首先,假设多项式次数 1 0 5 10^5 105,值域 1 0 9 10^9 109,那么朴素 FFT 光答案就高达 1 0 23 10^{23} 1023,就算你不损失精度也转不成 long long,况且中间值比这还要高几个量级。

MTT 采用拆系数的方法,即对于多项式 A ( x ) A(x) A(x),拆成两个多项式 M A 0 ( x ) + A 1 ( x ) MA_0(x)+A_1(x) MA0(x)+A1(x) 来减小精度压力。其中 M M M 为一较大整数,一般取 M O D \sqrt {MOD} MOD 。要注意模数 2 e 9 2e9 2e9 级别的时候不要偷懒用位运算, 2 15 2^{15} 215 2 16 2^{16} 216 都容易炸,并且复杂度瓶颈不在这,不用卡这点常。

然后你只需要求 ( M A 0 ( x ) + A 1 ( x ) ) ( M B 0 ( x ) + B 1 ( x ) ) = M 2 A 0 ( x ) B 0 ( x ) + M ( A 0 ( x ) B 1 ( x ) + A 1 ( x ) B 0 ( x ) ) + A 1 ( x ) B 1 ( x ) (MA_0(x)+A_1(x))(MB_0(x)+B_1(x))=M^2A_0(x)B_0(x)+M(A_0(x)B_1(x)+A_1(x)B_0(x))+A_1(x)B_1(x) (MA0(x)+A1(x))(MB0(x)+B1(x))=M2A0(x)B0(x)+M(A0(x)B1(x)+A1(x)B0(x))+A1(x)B1(x) 就可以了,这样答案是 1 0 14 10^{14} 1014 级别,开 long double 完全不虚。

这样一共需要 7 7 7 次 DFT,一般问题不大。接下来是利用高超的玄学技巧压到 4 4 4 次 DFT 的方法。

前置知识:普通 FFT 三次变两次

对于一般的实系数多项式乘法 A ( x ) ⋅ B ( x ) A(x)\cdot B(x) A(x)B(x),有一种优化方法是构造一个多项式 A ( x ) + i ⋅ B ( x ) A(x)+i\cdot B(x) A(x)+iB(x),因为是实系数,所以直接把 B ( x ) B(x) B(x) 的系数挂到虚部上就可以了。

对它做一次 DFT 然后自己乘自己,得到的是 A 2 ( x ) − B 2 ( x ) + 2 i ⋅ A ( x ) B ( x ) A^2(x)-B^2(x)+2i\cdot A(x)B(x) A2(x)B2(x)+2iA(x)B(x)

这样算出来的虚部除以 2 2 2 就是答案。

该算法将大量用到该思想。

DFT

考虑我们对两个实系数多项式 A ( x ) , B ( x ) A(x),B(x) A(x),B(x) 做 DFT,因为是实系数,我们把虚部在那里空着显得很浪费,尝试把这个优化成一次 DFT。

构造多项式 A ( x ) + i B ( x ) A(x)+iB(x) A(x)+iB(x),花费一次 DFT 得到 DFT ⁡ ( A ( x ) + i ( B ( x ) ) ) \operatorname{DFT}(A(x)+i(B(x))) DFT(A(x)+i(B(x)))。因为 DFT 的本质是点值,所以这个也等于 DFT ⁡ ( A ( x ) ) + i DFT ⁡ ( B ( x ) ) \operatorname{DFT}(A(x))+i\operatorname{DFT}(B(x)) DFT(A(x))+iDFT(B(x))

这个显然不是实部和虚部的关系了。现在我们需要不用 DFT 把这两个分离开。

考虑共轭虚多项式 A ( x ) − i B ( x ) A(x)-iB(x) A(x)iB(x),如果它的 DFT 可以快速得到,我们就可以简单地求出 A ( x ) A(x) A(x) B ( x ) B(x) B(x) 的 DFT。

然后又是愉快的推式子时间。

设两个多项式的系数为 a i , b i a_i,b_i ai,bi,代入一个单位根 ω n k \omega_n^k ωnk

DFT ⁡ ( A ( x ) + i B ( x ) ) = A ( ω n k ) + i B ( ω n k ) \operatorname{DFT}(A(x)+iB(x))=A(\omega_n^k)+iB(\omega_n^k) DFT(A(x)+iB(x))=A(ωnk)+iB(ωnk)

= ∑ j = 0 n − 1 a j ω n k j + i b j ω n k j =\sum_{j=0}^{n-1}a_j\omega_n^{kj}+ib_j\omega_n^{kj} =j=0n1ajωnkj+ibjωnkj

推不动了,考虑硬算。令 ω n k j = x j + i y j \omega_n^{kj}=x_j+iy_j ωnkj=xj+iyj

DFT ⁡ ( A ( x ) + i B ( x ) ) = ∑ j = 0 n − 1 a j ( x j + i y j ) + i b j ( x j + i y j ) \operatorname{DFT}(A(x)+iB(x))=\sum_{j=0}^{n-1}a_j(x_j+iy_j)+ib_j(x_j+iy_j) DFT(A(x)+iB(x))=j=0n1aj(xj+iyj)+ibj(xj+iyj)

= ∑ j = 0 n − 1 ( a j x j − b j y j ) + i ( a j y j + b j x j ) =\sum_{j=0}^{n-1}(a_jx_j-b_jy_j)+i(a_jy_j+b_jx_j) =j=0n1(ajxjbjyj)+i(ajyj+bjxj)

然后这不是三角函数公式吗?

所以 DFT 的某一点值的本质就是两个多项式对应项组成的向量 ( a j , b j ) (a_j,b_j) (aj,bj) 旋转同一角度后的向量和。

对于另一边 DFT ⁡ ( A ( x ) − i B ( x ) ) \operatorname{DFT}(A(x)-iB(x)) DFT(A(x)iB(x)),其对应的向量是 ( a j , − b j ) (a_j,-b_j) (aj,bj) ,也就是关于 x x x 轴对称。那么我们把上面那个角度倒着旋转,得到的向量和也是对称的。

翻译成人话, DFT 后翻转再共轭就得到了共轭虚多项式的 DFT。

注意是长度为 n n n 的翻转,所以 0 0 0 的位置需要特判。

这样,我们用两次 DFT 得到了 A 0 ( x ) , A 1 ( x ) , B 0 ( x ) , B 1 ( x ) A_0(x),A_1(x),B_0(x),B_1(x) A0(x),A1(x),B0(x),B1(x) 的 DFT。

然后我们就可以求出 A 0 ( x ) B 0 ( x ) , A 0 ( x ) B 1 ( x ) , A 1 ( x ) B 0 ( x ) , A 1 ( x ) B 1 ( x ) A_0(x)B_0(x),A_0(x)B_1(x),A_1(x)B_0(x),A_1(x)B_1(x) A0(x)B0(x),A0(x)B1(x),A1(x)B0(x),A1(x)B1(x) 的点值了。

IDFT

还是用一样的方法抱团变换。

考虑构造多项式 P ( x ) = A 0 ( x ) B 0 ( x ) + i A 0 ( x ) B 1 ( x ) P(x)=A_0(x)B_0(x)+iA_0(x)B_1(x) P(x)=A0(x)B0(x)+iA0(x)B1(x),注意是 IDFT 之后的,也就是这里设的是我们要求的答案。

因为我们知道 A 0 ( x ) B 0 ( x ) , A 0 ( x ) B 1 ( x ) A_0(x)B_0(x),A_0(x)B_1(x) A0(x)B0(x),A0(x)B1(x) 在所有单位根上的点值,所以可以直接求出 P ( x ) P(x) P(x) 的点值,然后做 IDFT,实部虚部就是 A 0 ( x ) B 0 ( x ) A_0(x)B_0(x) A0(x)B0(x) A 0 ( x ) B 1 ( x ) A_0(x)B_1(x) A0(x)B1(x)

另外两个同理,需要 2 2 2 次 IDFT。

总共做了 4 4 4 次 DFT,比三模数快了 8 8 8 倍。

注意输出的时候答案已经很大了,要先模了再乘 M M M

#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <cmath>
#define MAXN 262144+5
#define double long double
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
typedef long long ll;
const double Pi=acos(-1.0);
int p;
struct complex
{
	double x,y;
	inline complex(const double& x=0,const double& y=0):x(x),y(y){}	
};
inline complex operator +(const complex& a,const complex& b){return complex(a.x+b.x,a.y+b.y);}
inline complex operator -(const complex& a,const complex& b){return complex(a.x-b.x,a.y-b.y);}
inline complex operator *(const complex& a,const complex& b){return complex(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
inline complex rev(const complex& a){return complex(a.x,-a.y);}
complex rt[2][20];
int l,lim,r[MAXN];
inline void init(){lim=1<<l;for (int i=0;i<lim;i++) r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));}
inline void fft(complex* a,int type)
{
	for (int i=0;i<lim;i++) if (i<r[i]) swap(a[i],a[r[i]]);
	for (int L=0;L<l;L++)
	{
		int mid=1<<L,len=mid<<1;
		complex Wn=rt[type][L+1];
		for (int s=0;s<lim;s+=len)
		{
			complex w(1,0);
			for (int k=0;k<mid;k++,w=w*Wn)
			{
				complex x=a[s+k],y=w*a[s+mid+k];
				a[s+k]=x+y,a[s+mid+k]=x-y;
			}
		}
	}
	if (type) for (int i=0;i<lim;i++) a[i].x/=lim,a[i].y/=lim;
}
complex A[MAXN],B[MAXN],C[MAXN],D[MAXN];
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	for (int i=0;i<20;i++)
	{
		double a=2*Pi/(1<<i);
		rt[0][i]=rev(rt[1][i]=complex(cos(a),sin(a)));
	}
	int n,m;
	n=read(),m=read(),p=read();
	for (l=0;(1<<l)<=n+m;l++);
	init();
	for (int i=0;i<=n;i++)
	{
		int x=read();
		A[i]=complex(x>>15,x&((1<<15)-1));
	}
	for (int i=0;i<=m;i++)
	{
		int x=read();
		C[i]=complex(x>>15,x&((1<<15)-1));
	}
	fft(A,0),fft(C,0);
	B[0]=rev(A[0]),D[0]=rev(C[0]);
	for (int i=1;i<lim;i++) B[i]=rev(A[lim-i]),D[i]=rev(C[lim-i]);
	for (int i=0;i<lim;i++) 
	{
		complex a=A[i],b=B[i],c=C[i],d=D[i];
		A[i]=(a+b)*complex(0.5,0),B[i]=(a-b)*complex(0,-0.5);
		C[i]=(c+d)*complex(0.5,0),D[i]=(c-d)*complex(0,-0.5);
	}
	for (int i=0;i<lim;i++)
	{
		complex a=A[i],b=B[i],c=C[i],d=D[i];
		A[i]=a*c+a*d*complex(0,1);
		B[i]=b*c+b*d*complex(0,1);
	}
	fft(A,1),fft(B,1);
	for (int i=0;i<=n+m;i++)
	{
		ll ans=0;
		ll a=A[i].x+0.5,b=A[i].y+B[i].x+0.5,c=B[i].y+0.5;
		ans=(ans+((a%p)<<30))%p;
		ans=(ans+((b%p)<<15))%p;
		ans=(ans+c)%p;
		printf("%lld ",ans);
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值