[Luogu3803] 多项式乘法 [FFT/NTT]

题意
给定 n 次多项式 F(x) 和 m 次多项式 G(x)
求:F(x) ⊗ G(x) 。 n, m ≤ 106


链接
https://www.luogu.org/problemnew/show/P3803


FFT
长度为 2 n 2^n 2n 的情况下
按照下标奇偶分类
显然有 A ( x ) = A 1 ( x 2 ) + x A 2 ( x 2 ) A(x)=A_1(x^2)+xA_2(x^2) A(x)=A1(x2)+xA2(x2)
代入 ω n k = e 2 i π k n \omega_n^k=e^{\frac{2i\pi k}{n}} ωnk=en2iπk
A ( ω n k ) = A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) A(\omega_n^k)=A_1(\omega_n^{2k})+\omega_n^kA_2(\omega_n^{2k}) A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)
A ( ω n k + n / 2 ) = A 1 ( ω n 2 k ) − ω n k A 2 ( ω n 2 k ) = A ( − ω n k ) A(\omega_n^{k+n/2})=A_1(\omega_n^{2k})-\omega_n^kA_2(\omega_n^{2k})=A(-\omega_n^k) A(ωnk+n/2)=A1(ωn2k)ωnkA2(ωn2k)=A(ωnk)
(实际上我们有 ω n k + n / 2 = − ω n k \omega_n^{k+n/2} = -\omega_n^k ωnk+n/2=ωnk
逆变换只需要把单位根用其共轭复数替换,并将求出来的结果除以 n 。
( DFT 是多点求值,可以表示成系数矩阵乘上范德蒙德矩阵得到点值)
( IDFT 是反过来,那就是点值乘上范德蒙德矩阵的逆矩阵得到系数矩阵)
(然后求一求求一求搞一搞搞一搞就能得到关系了 -> Click Here
逆变换的具体推导过程也可以 -> Click Here
实现的时候我们把原数组下标二进制翻转就能直接得到递归到最底层的下标
利用它从最底层一层层向上合并即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
#include<ctime>
using namespace std;
#define R register
const int MAXN = 2097152+16;
int N, M, Limit = 1, Rev[MAXN], Log;
struct Complex
{
	double r, i;
	inline Complex(const double& a = 0, const double& b = 0)
	{
		r = a;
		i = b;
	}
	inline Complex operator + (const Complex& o) const
	{
		return Complex(r + o.r, i + o.i);
	}
	inline Complex operator - (const Complex& o) const
	{
		return Complex(r - o.r, i - o.i);
	}
	inline void operator += (const Complex& o)
	{
		r += o.r, i += o.i;
	}
	inline void operator -= (const Complex& o)
	{
		r -= o.r, i -= o.i;
	}
	inline Complex operator * (const Complex& o) const
	{
		return Complex(r * o.r - i * o.i, r * o.i + i * o.r);
	}
	inline void operator *= (const Complex& o)
	{
		*this = *this * o;
	}
}A[MAXN], B[MAXN], Wn[MAXN][2];
void FFT(Complex* A, const bool& Type = 0)
{
	static Complex t;
	for (R int i = 0; i < Limit; ++i) if(Rev[i] > i) swap(A[i], A[Rev[i]]);
	for (R int Mid = 1; Mid < Limit; Mid <<= 1)
	{
		for (R int Len = Mid << 1, Pos = 0; Pos < Limit; Pos += Len)
		{
			for (R int Delta = 0; Delta < Mid; ++Delta)
			{
				t = Wn[Limit/Len*Delta][Type] * A[Pos + Mid + Delta];
				A[Pos + Mid + Delta] = A[Pos + Delta] - t;
				A[Pos + Delta] += t;
			}
		}
	}
}
int main()
{
	scanf("%d%d", &N, &M);
	for (R int i = 0; i <= N; ++i) scanf("%lf", &A[i].r);
	for (R int i = 0; i <= M; ++i) scanf("%lf", &B[i].r);
	while (Limit <= N + M) Limit <<= 1, ++Log;
	for (R int i = 0; i < Limit; ++i) Rev[i] = (Rev[i>>1]>>1) | ((i&1)<<(Log - 1));
	const double tpi = 2.0 * acos(-1.0) / Limit;
	register double tmp;
	for (R int i = 0; i < Limit; ++i) tmp = tpi * i, Wn[i][0] = Complex(cos(tmp), sin(tmp)), Wn[i][1] = Complex(cos(tmp), -sin(tmp));
	FFT(A); FFT(B);
	for (R int i = 0; i < Limit; ++i) A[i] *= B[i];
	FFT(A, 1);
	for (R int i = 0; i <= N + M; ++i) printf("%d ", (int)(A[i].r/Limit+0.5));
	return 0;
}

NTT
单位复根实现 FFT 的重要性质:
1. ω n k ( 0 ≤ k &lt; n ) \omega_n^k(0\le k&lt;n) ωnk(0k<n) 互不相同 → 点值合法
敏感的话容易想到 g r ( m o d n ) ( 0 ≤ r &lt; n ) g^r\pmod{n}(0\le r&lt;n) gr(modn)(0r<n) 也是如此

假设 p = q n + 1 , ω n = g q p=qn+1,\omega_n=g^q p=qn+1,ωn=gq n n n 2 2 2 的幂。 ←所以 ntt 也跟 ftt 一样,需要长度为 2 n 2^n 2n
NTT 在 ( m o d p ) \pmod{p} (modp) 意义下进行。

2. ω 2 n 2 k = ω n k \omega_{2n}^{2k}=\omega_n^k ω2n2k=ωnk → 分治①
ω n = g q \omega_n=g^q ωn=gq 那么 ω 2 n = g q / 2 \omega_{2n}=g^{q/2} ω2n=gq/2 因为 p = q n + 1 = q 2 ⋅ 2 n + 1 p=qn+1=\frac{q}{2}\cdot{2n}+1 p=qn+1=2q2n+1
那么 ω 2 n 2 k = g k q = ω n k \omega_{2n}^{2k}=g^{kq}=\omega_n^k ω2n2k=gkq=ωnk

3. ω n k + n / 2 = − ω n k \omega_{n}^{k+n/2}=-\omega_n^k ωnk+n/2=ωnk
我们有 ω n k + n / 2 = g ( k + n / 2 ) q = g k q ⋅ g n q / 2 \omega_n^{k+n/2}=g^{(k+n/2)q}=g^{kq}\cdot g^{nq/2} ωnk+n/2=g(k+n/2)q=gkqgnq/2
g n q / 2 ≡ − 1 ( m o d p ) g^{nq/2}\equiv-1\pmod{p} gnq/21(modp) 是否成立呢?
显然 g n q = g p − 1 = g ϕ ( p ) ≡ 1 ( m o d p ) g^{nq}=g^{p-1}=g^{\phi(p)}\equiv1\pmod{p} gnq=gp1=gϕ(p)1(modp)
g n q / 2 = ± 1 g^{nq/2}=\pm1 gnq/2=±1 g 0 = 1 ≠ g n q / 2 g^0=1\ne g^{nq/2} g0=1̸=gnq/2 所以 g n q / 2 = − 1 g^{nq/2}=-1 gnq/2=1
那么 ω n k + n / 2 ≡ − ω n k ( m o d p ) \omega_n^{k+n/2\equiv-\omega_n^k}\pmod{p} ωnk+n/2ωnk(modp)

4. k ≠ 0 , ∑ r = 0 n − 1 ω n k r = 0 k\ne0,\sum\limits_{r=0}^{n-1}\omega_n^{kr}=0 k̸=0,r=0n1ωnkr=0
卜难证啊 一模一样

具体的 -> Click Here

然后换一换单位根就可以快乐 ntt
只要数据不会爆模数就可以啦?

998244353 = 119 ∗ 2 23 + 1 , g = 3 998244353=119*2^{23}+1,g=3 998244353=119223+1,g=3
1004535809 , g = 3 1004535809,g=3 1004535809,g=3
忘记了或者遇到没见过的 ntt 模数可以手打一个找原根?

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<cctype>
#include<ctime>
using namespace std;
const int MAXN = 2929229;
const long long p = 998244353;
const long long g = 3;
int n, m, Lim = 1, Log;
long long A[MAXN], B[MAXN], Wn[MAXN][2];
int Rev[MAXN];
long long qpowm(long long a, long long b)
{
	long long ret = 1;
	a %= p;
	while (b)
	{
		if (b & 1)
		{
			ret *= a;
			ret %= p;
		}
		a *= a;
		a %= p;
		b >>= 1;
	}
	return ret;
}
inline long long Adjust(long long x)
{
	x = (x % p) + p;
	return (x>=p)?(x-p):x;
}
void NTT(long long *a, const bool& Type = 0)
{
	for (register int i = 0; i < Lim; ++i) if (Rev[i] > i) swap(a[i], a[Rev[i]]);
	register long long t;
	for (register int Mid = 1; Mid < Lim; Mid <<= 1)
	{
		for (register int Len = Mid << 1, Pos = 0, qwq = Lim / Len; Pos < Lim; Pos += Len)
		{
			for (register int Sub = 0; Sub < Mid; ++Sub)
			{
				t = Wn[qwq*Sub][Type] * a[Pos + Mid + Sub];
				a[Pos + Mid + Sub] = Adjust(a[Pos + Sub] - t);
				a[Pos + Sub] = (a[Pos + Sub] + t) % p;
//				cout << a[Pos+Mid+Sub] << " " << a[Pos+Mid] << endl;
			}
		}
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	for (register int i = 0; i <= n; ++i) scanf("%lld", &A[i]);
	for (register int i = 0; i <= m; ++i) scanf("%lld", &B[i]);
	n += m;
	while (Lim <= n) Lim <<= 1, ++Log;
	for (register int i = 0; i < Lim; ++i) Rev[i] = (Rev[i>>1]>>1)|((i&1)<<(Log-1));
	const long long q = (p - 1) / Lim;
	register long long t = qpowm(g, q);
	const long long G = qpowm(qpowm(g, p-2), q);
	for (register int i = 0; i < Lim; ++i)
	{
		Wn[i][0] = (!i) ? 1 : (Wn[i-1][0] * t % p);
		Wn[i][1] = (!i) ? 1 : (Wn[i-1][1] * G % p);
//		cout << Wn[i][0] << " " << Wn[i][1] << endl;
	}
	NTT(A); NTT(B);
	for (register int i = 0; i < Lim; ++i) A[i] = A[i] * B[i] % p;
	NTT(A, 1);
	t = qpowm(Lim, p - 2);
	for (register int i = 0; i <= n; ++i) printf("%lld ", A[i] * t % p);
	return 0;
}

(无关)
才知道f社出新作了
火星了 两 个 月(震声)
我自尽罢

我去撒库拉抹油了.jpg

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值