任意模数NTT学习小记

这篇博客探讨了如何在非NTT模数下计算多项式乘积的问题,介绍了拆系数FFT方法及其优化,通过将DFT次数减少到两次,并利用复数共轭性质进一步优化算法,最终实现只需四次DFT的解决方案。
摘要由CSDN通过智能技术生成

问题

求两个多项式 A ( x ) A(x) A(x) B ( x ) B(x) B(x)对一个不是NTT模数的数取模的结果。

拆系数FFT

设置一个阈值 W W W(通常设置为 2 15 2^{15} 215),将 A ( x ) , B ( x ) A(x),B(x) A(x),B(x)拆分成 A = a W + b , B = c W + d A=aW+b,B=cW+d A=aW+b,B=cW+d,其中 a , b , c , d a,b,c,d a,b,c,d均为多项式。则 A B = a c W 2 + ( a d + b c ) W + b d AB=acW^2+(ad+bc)W+bd AB=acW2+(ad+bc)W+bd

需要做 7 7 7次DFT。

将FFT中DFT的次数优化为两次

对于两个实系数多项式 A ( x ) , B ( x ) A(x),B(x) A(x),B(x),现在要计算它们的乘积。设 F ( x ) = A ( x ) + i B ( x ) , G ( x ) = A ( x ) − i B ( x ) F(x)=A(x)+iB(x),\quad G(x)=A(x)-iB(x) F(x)=A(x)+iB(x),G(x)=A(x)iB(x)

ω \omega ω表示 n n n次单位根, F D F T ( k ) = F ( ω k ) , G D F T ( k ) = G ( ω k ) F_{DFT}(k)=F(\omega^k),G_{DFT}(k)=G(\omega^k) FDFT(k)=F(ωk),GDFT(k)=G(ωk)。则显然有 F D F T ( k ) = A ( ω k ) + i B ( ω k ) = ∑ j = 0 n − 1 ( a j + i b j ) ω j k F_{DFT}(k)=A(\omega^k)+iB(\omega^k)=\sum_{j=0}^{n-1}(a_j+ib_j)\omega^{jk} FDFT(k)=A(ωk)+iB(ωk)=j=0n1(aj+ibj)ωjk

考虑如何通过 F F F的点值得到 G G G的点值。令 c o n j ( w ) conj(w) conj(w)表示 w w w的共轭,那么 G D F T ( k ) = ∑ j = 0 n − 1 ( a j − i b j ) ω j k = ∑ j = 0 n − 1 ( a j − i b j ) ( cos ⁡ 2 π j k n + i sin ⁡ 2 π j k n ) = ∑ j = 0 n − 1 ( a j cos ⁡ 2 π j k n + b j sin ⁡ 2 π j k n ) + i ( a j sin ⁡ 2 π j k n − b j cos ⁡ 2 π j k n ) = c o n j ( ∑ j = 0 n − 1 ( a j cos ⁡ − 2 π j k n − b j sin ⁡ − 2 π j k n ) + i ( a j sin ⁡ − 2 π j k n + b j cos ⁡ − 2 π j k n ) ) = c o n j ( ∑ j = 0 n − 1 ( a j + i b j ) ( cos ⁡ − 2 π j k n + i sin ⁡ − 2 π j k n ) ) = c o n j ( ∑ j = 0 n − 1 ( a j + i b j ) ω − j k ) = c o n j ( ∑ j = 0 n − 1 ( a j + i b j ) ω j ( n − k ) ) = c o n j ( F D F T ( n − k ) ) \begin{aligned} G_{DFT}(k)&=\sum_{j=0}^{n-1}(a_j-ib_j)\omega^{jk}\\ &=\sum_{j=0}^{n-1}(a_j-ib_j)\Big(\cos\frac{2\pi jk}{n}+i\sin\frac{2\pi jk}{n}\Big)\\ &=\sum_{j=0}^{n-1}\Big(a_j\cos\frac{2\pi jk}{n}+b_j\sin\frac{2\pi jk}{n}\Big)+i\Big(a_j\sin\frac{2\pi jk}{n}-b_j\cos\frac{2\pi jk}{n}\Big)\\ &=conj\Big(\sum_{j=0}^{n-1}\Big(a_j\cos\frac{-2\pi jk}{n}-b_j\sin\frac{-2\pi jk}{n}\Big)+i\Big(a_j\sin\frac{-2\pi jk}{n}+b_j\cos\frac{-2\pi jk}{n}\Big)\Big)\\ &=conj\Big(\sum_{j=0}^{n-1}(a_j+ib_j)\Big(\cos\frac{-2\pi jk}{n} + i\sin\frac{-2\pi jk}{n}\Big)\Big)\\ &=conj\Big(\sum_{j=0}^{n-1}(a_j+ib_j)\omega^{-jk}\Big)\\ &=conj\Big(\sum_{j=0}^{n-1}(a_j+ib_j)\omega^{j(n-k)}\Big)\\ &=conj\Big(F_{DFT}(n-k)\Big) \end{aligned} GDFT(k)=j=0n1(ajibj)ωjk=j=0n1(ajibj)(cosn2πjk+isinn2πjk)=j=0n1(ajcosn2πjk+bjsinn2πjk)+i(ajsinn2πjkbjcosn2πjk)=conj(j=0n1(ajcosn2πjkbjsinn2πjk)+i(ajsinn2πjk+bjcosn2πjk))=conj(j=0n1(aj+ibj)(cosn2πjk+isinn2πjk))=conj(j=0n1(aj+ibj)ωjk)=conj(j=0n1(aj+ibj)ωj(nk))=conj(FDFT(nk))

F , G F,G F,G的点值求出来,就可以解出 A , B A,B A,B的点值。从而把FFT中DFT的次数从三次优化为两次。

对拆系数FFT的优化

从上面的推导容易发现,若知道一个多项式的点值,则可以求出其共轭多项式的点值。将这个优化用于拆系数FFT。

注意到 ( a + b i ) ( c + d i ) = ( a c − b d ) + i ( a d + b c ) (a+bi)(c+di)=(ac-bd)+i(ad+bc) (a+bi)(c+di)=(acbd)+i(ad+bc)

( a − b i ) ( c + d i ) = ( a c + b d ) + i ( a d − b c ) (a-bi)(c+di)=(ac+bd)+i(ad-bc) (abi)(c+di)=(ac+bd)+i(adbc)

可以先求出 ( a + b i ) (a+bi) (a+bi) ( c + d i ) (c+di) (c+di)的点值。由于 ( a + b i ) (a+bi) (a+bi) ( a − b i ) (a-bi) (abi)互为共轭,可以通过 ( a + b i ) (a+bi) (a+bi)的点值推出 ( a − b i ) (a-bi) (abi)的点值。求出上面两式后再IDFT回去,就可以得到 a c , b d , a d + b c ac,bd,ad+bc ac,bd,ad+bc的值了。这样总共需要做四次DFT。

代码

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int W = 32768;
const int N = 400005;
const long double pi = acos(-1);

int n, m, p, rev[N], L, ans[N];

struct com
{
    long double x,y;
    com operator + (const com & d) const {return (com){x + d.x, y + d.y};}
    com operator - (const com & d) const {return (com){x - d.x, y - d.y};}
    com operator * (const com & d) const {return (com){x * d.x - y * d.y, x * d.y + y * d.x};}
    com operator / (const long double & d) const {return (com){x / d, y / d};}
    com operator ~ () const {return (com){x, -y};}
}A[N], B[N], C[N];

void pre()
{
	int lg = 0;
	for (L = 1; L <= n + m; L <<= 1, lg++);
	for (int i = 0; i < L; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lg - 1));
}

LL num(long double x)
{
	return x < 0 ? (LL)(x - 0.5) : (LL)(x + 0.5);
}

void fft(com * a, int f)
{
	for (int i = 0; i < L; i++) if (i < rev[i]) swap(a[i], a[rev[i]]);
	for (int i = 1; i < L; i <<= 1)
	{
		com wn = (com){cos(pi / i), f * sin(pi / i)};
		for (int j = 0; j < L; j += (i << 1))
		{
			com w = (com){1, 0};
			for (int k = 0; k < i; k++)
			{
				com u = a[j + k], v = a[j + k + i] * w;
				a[j + k] = u + v; a[j + k + i] = u - v;
				w = w * wn;
			}
		}
	}
	if (f == -1) for (int i = 0; i < L; i++) a[i] = a[i] / L;
}

int main()
{
	scanf("%d%d%d", &n, &m, &p);
	for (int i = 0; i <= n; i++)
	{
		int x; scanf("%d", &x);
		A[i] = (com){x / W, x % W};
	}
	for (int i = 0; i <= m; i++)
	{
		int x; scanf("%d", &x);
		C[i] = (com){x / W, x % W};
	}
	pre();
	fft(A, 1); fft(C, 1);
	for (int i = 0; i < L; i++) B[i] = ~A[(L - i) % L];
	for (int i = 0; i < L; i++) A[i] = A[i] * C[i], B[i] = B[i] * C[i];
	fft(A, -1); fft(B, -1);
	for (int i = 0; i <= n + m; i++)
	{
		LL x1 = num(A[i].x), x2 = num(B[i].x), y1 = num(A[i].y), y2 = num(B[i].y);
		LL ac = (x1 + x2) / 2, bd = x2 - ac, bcad = y1;
		ans[i] = (ac % p * W % p * W % p + bcad * W % p + bd % p) % p;
	}
	for (int i = 0; i <= n + m; i++) printf("%d ", ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值