Live2D

Note -「多项式」基础模板(FFT/NTT/多模 NTT)光速入门

  进阶篇戳这里

ω 何为「多项式」ω

  多项式(polynomial)是指由变量(variable)、系数(coefficient)以及它们之间的加、减、乘、幂运算(非负整数次方)得到的表达式。多项式就是整式

ω 基本概念 ω

  在初中阶段就有所接触:整式可以包含加、减、乘、除、乘方五种对变量的运算,同时要求除数不含有变量。设 x,y 为变量,其余字母为常量,则 xy,a+by 是整式,ax+πy (a0) 也是整式,因为根号下的 a 是常量;xy,xy 则不是整式。

  在本文中,我们所说的多项式通常指一元多项式,即仅含一个变量 x 的多项式。并简记一个多项式 A(x)=a0+a1x+a2x2++anxn,其中 nA(x)次数

ω 系数表示法 & 点值表示法 ω

  在上文中 A(x)=a0+a1x+a2x2++anxn 就是多项式的系数表示法。而可以证明,对于一个 n 次多项式,可以用任意 n+1 个其函数图像上不重合的点来唯一确定这个多项式。我们任取 n+1 个横坐标的值 x0,x1,,xn,代入 A(x),求得 n+1 个点 (x0,y0),(x1,y1),,(xn,yn),就可以用这 n+1 个点唯一确定 A(x),这也被称为 A(x)点值表示法。注意到 x 是任意的,所以同一个多项式的点值表示法不唯一

  接下来,抛出我们的问题:设有两个多项式 A(x),B(x),如何计算它们的乘积(亦称作卷积) C(x)=A(x)B(x) 呢?

  一个简单的想法,我们可以利用乘法分配律求到 C(x) 每一项的系数。于是:

C(x)=i=0+(j+k=iajbk)xi

  注意求和中的 + 只是一种形式记法,事实上,当 i 大于 A(x) 的次数与 B(x) 的次数之和时,xi 的系数 ci 显然为 0

  不过呢……从算法设计的角度,这种求多项式乘法的方式的复杂度是 O(n2) 的,好慢 qwq。

  联系到点值表示法,如果对于同一个横坐标 x,其在 A(x) 上的坐标为 (x,ya),在 B(x) 上的坐标为 (x,yb),那么代入最初的表达式,它在 C(x)=A(x)B(x) 上的坐标就是 (x,yayb) 呐。所以,在已知 A(x)B(x) 的点值表示法时,我们可以用 O(n) 的时间求出 C(x) 的点值表示法!

ω 傅里叶(Fourier)变换 ω

  我们继续解决上文多项式乘法的问题——找到一个高效的算法,实现系数与点值表示法的转换

  本节中,若非特别说明,n=2k,kN

ω 概述 ω

  离散傅里叶变换(Discrete Fourier Transform,DFT),是傅里叶变换在时域和频域上都呈离散的形式,将信号的时域采样变换为其 DTFT 的频域采样。

  FFT 是一种高效实现 DFT 的算法,称为快速傅立叶变换(Fast Fourier Transform,FFT)。它对傅里叶变换的理论并没有新的发现,但是对于在计算机系统或者说数字系统中应用离散傅立叶变换,可以说是进了一大步。

ω 前置知识 - 复数 ω

  其实很简单:我们把形如 z=a+bi 的数称为复数,其中 a,bRa实部b虚部ii2=1,为虚数单位。我们可以把复数与二维平面上的向量 (a,b) 一一对应,此时 x 轴称为实轴y 轴称为虚轴。并记其与 x 轴正方向的夹角为辐角 θ,向量的模长为 |z|。如图,对于 z=3+2i

tmp.png

  其中 l 为模长,θ 为辐角。接下来我们研究复数的一些基本运算:

  • 加减法:z1±z2=(a1±a2)+(b1±b2)i

  • 乘法:z1z2=(a1a2b1b2)+(a1b2+a2b1)i

      进一步研究,发现:

    |z1z2|2=(a12a222a1a2b1b2+b12b22)+(a12b22+2a1a2b1b2+a22b12)=(a12+b12)(a22+b22)

      最后一步用了简单的因式分解技巧。由于 a12+b12=|z1|2,a22+b22=|z2|2,所以有 |z1z2|=|z1||z2|

      此外,运用 tan 的和角公式,还可以求到 θz1z2=θz1+θz2

      所以,复数的乘法有一个重要的几何意义:模长相乘,辐角相加

      最后,我们定义 z¯z 关于 x 轴对称的向量,称为 z共轭复数。即若 z=a+bi,有 z¯=abi。可以发现 zz¯=a2+b2R。 为后文方便叙述,记 conj(z)=z¯

ω 单位根 ω

  定义 n 次单位根为所有满足 zn=1z。根据乘法的几何意义,可知 |z|=1nθz=2kπ,其中 kZ。于是满足条件且小于 2πθz 集合为 {0,1×2πn,2×2πn,,(n1)×2πn},共 n 个。我们记 ωnkω,/'əʊmɪɡə/)为第 kn 次单位根(k=0,1,,n1)。如图,当 n=8 时:

tmp.png

  运用简单的三角函数知识,得到:

ωnk=(cos2πkn,sin2πkn)

  可以发现,单位根实际上体现了一个周期意义:ωnn=ωn0。所以此后单位根的上标(乘方或是编号,它们在运算意义上等价)默认对单位根次数取模。单位根有如下性质:

  • ωnjωnk=ωnj+k
  • ωknkj=ωnj
  • ω2nk=ω2nk+n

  可以结合乘法几何意义与公式理解。

ω 快速傅里叶正变换(FFT)ω

  有必要重申我们的目标:实现系数与点值表示法的转换。

  现在,设有 A(x)=a0+a1x+a2x2++an1xn1,我们希望代入 n 次单位根 ωn0,ωn1,,ωnn1n 个复数来取得 A(x) 的点值表示法。

  分开奇数项与偶数项,记:

Ae(x)=i[0,n)2|iaixi2

Ao(x)=i[0,n)2|iaixi12

  利用 Ae(x)Ao(x) 来表示 A(x),有:

A(x)=Ae(x2)+xAo(x2)

  设 n=2m,尝试代入一个 ωnk

A(ωnk)=Ae(ωn2k)+ωnkAo(ωn2k)

  注意到 ωn2k=ω2m2k=ωmk,所以:

A(ωnk)=Ae(ωmk)+ωnkAo(ωmk)

  类似地,代入 ωnk+m=ωnk,有:

A(ωnk+m)=Ae(ωmk)ωnkAo(ωmk)

  所以,当已知 Ae(ωmk)Ao(ωmk) 时,可以 O(1) 计算出 A(ωnk)A(ωnk+m)。现在,就有了一个算法雏形:

Function name: FFT.

Input: a polynomial A(x)=a0+a1x1+ which's length is n (n=2k,kN).

Output: a set A={A(ωn0),A(ωn1),,A(ωnn1)}.

if n=1 then

 return {(a0,0)}

mn2

Ae(x)i[0,n)2|iaixi2

Ao(x)i[0,n)2|iaixi12

A

AeFFT(Ae,m)

AoFFT(Ao,m)

k0

while k<m do

AkAek+ωnkAok

​ Ak+mAekωnkAok

​ kk+1

return A

  复杂度是 T(n)=2T(n2)+O(n)=O(nlogn)。在后文中,“多项式 A(x) 的点值表示”均指由上述 FFT 算法得到的 A

  别着急做题,这种实现方法 SPFA 了 qwq。

ω 快速傅里叶逆变换(IFFT)ω

  利用 FFT,我们可以快速地将系数表示法转换成点值表示法,那怎么在同样优秀的复杂度内转回去呢?

  从本质入手,考虑到 Aj=k=0n1ak(ωnj)k,我们把它写成矩阵乘法的形式:

[(ωn0)0(ωn0)1(ωn0)n1(ωn1)0(ωn1)0(ωn1)n1(ωnn1)0(ωnn1)1(ωnn1)n1][a0a1an1]=[A0A1An1]

  令左侧的系数矩阵为 U,考虑系数矩阵 V

V=[(ωn0)0(ωn0)1(ωn0)n1(ωn1)0(ωn1)0(ωn1)n1(ωn(n1))0(ωn(n1))1(ωn(n1))n1]

  那么:

(UV)ij=k=0n1uikvkj=k=0n1ωnk(ji)

  当 i=j 时,显然 (UV)ij=n

  当 ij 时,发现是等比数列的形式。所以 (UV)ij=1ωnn(ji)1ωnji。又因为 ωnn=ωn0=1,所以分子有 11=0,则原式 =0

  综上,(UV)ij=[i=j]n,即 1nV=U1

  以此,就有:

[a0a1an1]=1nV[A0A1An1]=1n[(ωn0)0(ωn0)1(ωn0)n1(ωn1)0(ωn1)0(ωn1)n1(ωn(n1))0(ωn(n1))1(ωn(n1))n1][A0A1An1]

  注意到 ωnk=ωnnk,所以依然能够用 FFT 的递归形式求解。

ω 迭代实现 ω

  常数惊人呐……

  这里,我们将尝试把递归的 FFT Fast-Fast TLE 转换为仅用循环迭代的 FFT。

  观察递归时各系数的位置:

  观察第一层和最后一层:

原序列 dec 0 1 2 3 4 5 6 7
原序列 bin 000 001 010 011 100 101 110 111
新序列 dec 0 4 2 6 1 5 3 7
新序列 bin 000 100 010 110 001 101 011 111

  发现原序列下标的二进制翻转过来就是新序列下标。

  所以我们可以先安排好新序列下标,再从下往上迭代就可以了。

ω 例题 ω

ω「洛谷 P3803」「模板」多项式乘法(FFT)ω

ω 题意简述 ω

  link.

  给两个多项式的每项系数,求其相乘得到的多项式的每项系数。

ω 数据规模 ω

  多项式长度 n,m106

ω Solution ω

  直接给出完整代码,建议先模仿实现,以免常数惊人 owo。

  不过我不知道怎么折叠显示代码,影响阅读很抱歉 qwq。

ω Code ω

#include <cmath>
#include <cstdio>
#include <iostream>

inline int rint () {
	int x = 0, f = 1; char s = getchar ();
	for ( ; s < '0' || '9' < s; s = getchar () ) f = s == '-' ? -f : f;
	for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
	return x * f;
}

template<typename Tp>
inline void wint ( Tp x ) {
	if ( x < 0 ) putchar ( '-' ), x = -x;
	if ( 9 < x ) wint ( x / 10 );
	putchar ( x % 10 ^ '0' );
}

const int MAXL = 1 << 21;
const double PI = acos ( -1 );
int n, m, rev[MAXL + 5];

struct Complex { // 复数结构体。
	double x, y;
	Complex () {} Complex ( const double tx, const double ty ): x ( tx ), y ( ty ) {}
	inline Complex operator + ( const Complex t ) const { return Complex ( x + t.x, y + t.y ); }
	inline Complex operator - ( const Complex t ) const { return Complex ( x - t.x, y - t.y ); }
	inline Complex operator * ( const Complex t ) const { return Complex ( x * t.x - y * t.y, x * t.y + y * t.x ); }
	inline Complex operator / ( const double t ) const { return Complex ( x / t, y / t ); }
} A[MAXL + 5], B[MAXL + 5];

inline void FFT ( const int n, Complex* A, const int tp ) {
	// n为多项式长度,保证是2的整数幂;A为每项系数;tp=1表示正变换,tp=-1表示逆变换。
	for ( int i = 0; i < n; ++ i ) if ( i < rev[i] ) std :: swap ( A[i], A[rev[i]] ); // 安排系数位置。
	for ( int i = 2, stp = 1; i <= n; i <<= 1, stp <<= 1 ) {
		Complex w ( cos ( PI / stp ), tp * sin ( PI / stp ) ); // 当前正在处理$\omega_i$。
		for ( int j = 0; j < n; j += i ) {
			Complex r ( 1, 0 );
			for ( int k = 0; k < stp; ++ k, r = r * w ) {
				// $r=\omega_i^k$。
				Complex ev ( A[j + k] ), ov ( r * A[j + k + stp] );
				A[j + k] = ev + ov, A[j + k + stp] = ev - ov;
			}
		}
	}
	if ( ! ~ tp ) for ( int i = 0; i < n; ++ i ) A[i] = A[i] / n; // 逆变换,最后每项/n。
}

inline void ployConv ( const int n, const int m, const Complex* A, const Complex* B, Complex* C ) {
	// 计算A(x)与B(x)的卷积,答案为C(x)。
	int lg = 0, len = 1;
	static Complex tmpA[MAXL + 5], tmpB[MAXL + 5];
	for ( ; len < n + m - 1; len <<= 1, ++ lg ); // C(x)长度为n+m-1,找到适合的长度len。
	for ( int i = 0; i < len; ++ i ) {
		tmpA[i] = A[i], tmpB[i] = B[i];
		rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << lg >> 1 ); // rev[i]为i翻转后的位置,自行理解。
	}
	FFT ( len, tmpA, 1 ), FFT ( len, tmpB, 1 ); // 正变换求到点值表示。
	for ( int i = 0; i < len; ++ i ) C[i] = tmpA[i] * tmpB[i]; // 求到C(x)的点值表示。
	FFT ( len, C, -1 ); // 逆变换求到C(x)。
}

int main () {
	n = rint () + 1, m = rint () + 1; // 注意这里要+1。
	for ( int i = 0; i < n; ++ i ) A[i].x = rint ();
	for ( int i = 0; i < m; ++ i ) B[i].x = rint ();
	ployConv ( n, m, A, B, A );
	for ( int i = 0; i < n + m - 1; ++ i ) {
		wint ( ( int ) round ( A[i].x ) );
		putchar ( i == n + m - 2 ? '\n' : ' ' );
	}
	return 0;
}

ω 快速数论变换(NTT)ω

  事实上,由于 FFT 存在大量浮点数运算,其精度和常数都不尽人意。那么能否在整数域下找到单位根的替代品呢?

  我们来总结一下单位根应具有的性质:

  • ωnjωnk=ωnj+k,注意这里的上标就意义上来说不是乘方,而是编号(当然运算上就是乘方 www)。
  • ωknkj=ωnj,我们需要这个性质进行递归处理。
  • ω2nk=ω2nk+n,这一性质体现在了合并计算的步骤。
  • (jk)(ωnjωnk),它保证了点值表示法的坐标不重合。
  • ωn0=1,它保证了逆变换顺利进行。

  考虑在模意义下……

ω 原根 ω

  OI-Wiki 讲得好高深 qwq……

  对于素数 p,定义其原根 g 为满足 (j,k[0,p1),jk)(gjgk(modp)) 的数。模仿 ω 的定义,我们令 αnk=(gp1n)k(这里 α 是随便取的名字 www),考虑其是否满足上述几条性质:

  • αnjαnkαnjk(modp),乘方性质,显然。
  • αknkj=(gp1kn)kj=(gp1n)j=αnj,成立。
  • α2nk+n=g(p1)(k+n)2n=(gp12)g(p1)k2ng(p1)k2n=α2nk(modp)。注意因为 (gp12)21(modp) 且根据 g 的定义, gp12=α2nnα2n0α2n01(modp),所以 gp121(modp),等式成立。
  • 由定义,成立。
  • 显然 g0,成立。

  于是,把 FFT 中的所有 ωnk 替换为 αnk 就变成 NTT 啦!记得取模 owo。

ω 实现 ω

inline void NTT ( const int n, int* A, const int tp ) {
	for ( int i = 0; i < n; ++ i ) if ( i < rev[i] ) A[i] ^= A[rev[i]] ^= A[i] ^= A[rev[i]];
	for ( int i = 2, stp = 1, w; i <= n; i <<= 1, stp <<= 1 ) {
		w = qkpow ( G, ( MOD - 1 ) / i );
              // G为原根,qkpow(a,b)为a的b次方模MOD的结果。
		if ( ! ~ tp ) w = qkpow ( w, MOD - 2 );
		for ( int j = 0; j < n; j += i ) {
			for ( int r = 1, k = j; k < j + stp; ++ k, r = 1ll * r * w % MOD ) {
				int ev = A[k], ov = 1ll * r * A[k + stp] % MOD;
				A[k] = ( ev + ov ) % MOD, A[k + stp] = ( ev - ov + MOD ) % MOD;
			}
		}
	}
	if ( ! ~ tp ) {
		int invn = qkpow ( n, MOD - 2 ); // invn为n的逆元。
		for ( int i = 0; i < n; ++ i ) A[i] = 1ll * A[i] * invn % MOD;
	}
}

ω NTT 模数 ω

  注意到,n 的大小是随多项式长度变化的,而我们必须保证 n|(p1),所以 p1 所含 2 的因子个数决定了 NTT 能够接受的最大多项式长度。对于素数 p 的选取自然也有了考究。

  记住一点,9982443531=223×119适合做 NTT 模数,原根为 3109+71=2×500000003不适合做 NTT 模数

  更多 NTT 模数可见这篇博客

  到此,多项式最最基础的知识就结束啦。

ω 奇怪的模数 - 任意模数 NTT ω

  你 NTT 对模数有要求对吧?出题人就笑了……/xyx

  好吧,在不接受 FFT 精度误差的情况下,我们来想想办法应对模数不是 NTT 模数,甚至不是素数的毒瘤题吧!

ω 三模 NTT ω

  我偏要用 NTT 模数 www。

  顾名思义,我们任取三个 NTT 模数 p1,p2,p3,分别求出多项式乘积的第 k 项模 p1,p2,p3 的结果,然后用中国剩余定理(CRT)解出该项模题目给定模数 M 的结果就行了。不熟悉 CRT 的读者可以参考这篇博客

  我个人认为不太优美,因此没有具体实现。在抱歉之余推荐这篇博客的模板 owo。

ω 拆系数 FFT(MTT)ω

  我偏要用 FFT。

  缩写释义:MTT - MaoXiao Theory(?) Transformation。快速毛论变换(雾。

  假设多项式 A(x)=a0+a1x+a2x2+,B(x)=b0+b1x+b2x2+,长度均为 n,给定的模数 m,我们要求它们的卷积 R(x)=A(x)B(x)。如果直接 FFT,最大的系数可达到 nm2 的级别。我们假定 n105,m109,那么这样的精度误差是不被接受的。我们取 M=m,并定义以下几个多项式:

C(x)=k=0n1(akmodM)xkD(x)=k=0n1ak(akmodM)MxkE(x)=k=0n1(bkmodM)xkF(x)=k=0n1ak(akmodM)Mxk

  可以发现,以上每个多项式的系数都是不超过 M 的,那么任意两个多项式卷积的最大系数不超过 nM21014,在 double 的承受范围以内。(保险起见,建议开 long double,不过对于某些 g++ 版本貌似并没有作用 owo。)我们用这四个多项式表示 R(x)

R(x)=M2D(x)F(x)+M(C(x)F(x)+D(x)E(x))+C(x)E(x)

  这样,在 FFT 过程中,系数大小能被接受;作系数拼凑时步步取模亦能保证不溢出,我们就用 FFT 解决了任意模数 NTT 的问题。一般来说,取 M=32768=215,可以用位运算节省常数;还应利用 ωnk 的通项预处理出所有单位根以保障精度。

ω 七次转五次 ω

  数一数,我们对 C(x),D(x),E(x),F(x) 分别进行了一次 FFT,对 D(x)F(x), C(x)F(x)+D(x)E(x), C(x)E(x) 分别进行了一次 IFFT,一共有七次变换操作,常数有点点大 qwq。

  接下来,我们假设有实系数多项式 A(x)=a0+a1x+a2x2+,B(x)=b0+b1x+b2x2+,长度均为 n (n=2k,kN),我们希望分别求到它们的点值表示 AB。于是定义:

P(x)=A(x)+iB(x)

Q(x)=A(x)iB(x)

  其中 i 是虚数单位。设 PQ 的点值表示分别为 P,Q,并记 ωnk 的辐角 2πknθnk。那么:

Pk=A(ωnk)+iB(ωnk)=j=0n1ajωnk+ibjωnk=j=0n1(aj+ibj)(cosθnk+isinθnk)Qk=A(ωnk)iB(ωnk)=j=0n1ajωnjkibjωnjk=j=0n1(ajibj)(cosθnjk+isinθnjk)=j=0n1(ajcosθnjk+bjsinθnjk)+i(ajsinθnjkbjcosθnjk)=conj(j=0n1(ajcosθnjk+bjsinθnjk)i(ajsinθnjkbjcosθnjk))=conj(j=0n1(ajcos(θnjk)bjsin(θnjk))+i(ajsin(θnjk)bjcos(θnjk)))=conj(j=0n1(aj+ibj)(cos(θnjk)+isin(θnjk)))=conj(j=0n1(aj+ibj)(cosθnjk+isinθnjk))=conj(j=0n1(aj+ibj)(cosθn(nk)j+isinθn(nk)j))=conj(Pnk)

  推导时,时刻留意 θn 的上标在模 n 意义下嗷!

  最终,发现 Q 可以在求得 P 的前提下 O(1) 求到。只需要用 PQ 表示出 AB

Ak=Pk+Qk2Bk=iQkPk2

  就可以用一次 FFT 求出两个多项式的点值表示啦!(我认为毛啸的论文在 Bk 的表达式处有误,右侧分子应为本文的 QkPk 而非 PkQk,欢迎指正 qwq。)

  于是,我们可以把对 C(x),D(x),E(x),F(x) 的 FFT 合并为两组,就只需要五次 FFT 啦!

ω 五次转四次 ω

  正变换已经足够优秀了,我们接下来考虑逆变换的合并。按照上文的推导,我们只需要求到 P(x) 或者 Q(x) 就可以了——因为原多项式是实系数的,所以对于 P(x)Q(x) 的每一项,我们能够区分出其实部就是 A(x) 的系数而虚部就是 B(x) 的系数。既然已知 AB,直接利用定义式,有:

Pk=Ak+iBk

  逆变换 P 得到 P(x),就可以把两次逆变换合并为一次了。最终,我们把四次正变换合并为两次,三次逆变换合并为两次,仅用四次 FFT 就解决了这一问题。

ω 例题 ω

ω 「洛谷 P4245」「模板」任意模数 NTT

ω 题意简述 ω

  link.

  给两个多项式,求其在模 p 意义下的卷积。

ω 数据规模 ω

  多项式长度 n105,系数 ak,bk109p109+9

ω Solution ω

  这里仅给出四次 FFT 的 MTT 做法。

ω Code ω

#include <cmath>
#include <cstdio>
#include <iostream>

typedef long long LL;

inline int rint () { /* 快读 */ }

inline void wint ( const int x ) { /* 快输 */ }

const int MAXN = 1 << 18;
const double PI = acos ( -1 );
int n, m, p, A[MAXN + 5], B[MAXN + 5], rev[MAXN + 5];

struct Complex {
	/* 复数结构体 */
} omega[MAXN + 5], P[MAXN + 5], Q[MAXN + 5], C[MAXN + 5], D[MAXN + 5], E[MAXN + 5], F[MAXN + 5];

inline void FFT ( const int n, Complex* A, const int tp ) { /* FFT,应使用预处理好的单位根omega[]。 */ }

int main () {
	n = rint () + 1, m = rint () + 1, p = rint ();
	for ( int i = 0; i < n; ++ i ) A[i] = rint () % p, P[i] = Complex ( A[i] & 0x7fff, A[i] >> 15 );
	for ( int i = 0; i < m; ++ i ) B[i] = rint () % p, Q[i] = Complex ( B[i] & 0x7fff, B[i] >> 15 );
	int lg = 0, len = 1;
	for ( ; len < n + m - 1; len <<= 1, ++ lg );
	for ( int i = 0; i < len; ++ i ) rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << lg >> 1 );
	for ( int i = 1; i < len; i <<= 1 ) { // 预处理单位根。
		for ( int k = 0; k < i; ++ k ) {
			omega[len / i * k] = Complex ( cos ( PI * k / i ), sin ( PI * k / i ) );
		}
	}
	FFT ( len, P, 1 ), FFT ( len, Q, 1 );
	for ( int i = 0; i < len; ++ i ) {
		Complex t ( P[( len - i ) % len].x, -P[( len - i ) % len].y );
		C[i] = ( P[i] + t ) / 2, D[i] = Complex ( 0, 1 ) * ( t - P[i] ) / 2;
	}
	for ( int i = 0; i < len; ++ i ) {
		Complex t ( Q[( len - i ) % len].x, -Q[( len - i ) % len].y );
		E[i] = ( Q[i] + t ) / 2, F[i] = Complex ( 0, 1 ) * ( t - Q[i] ) / 2;
	}
	for ( int i = 0; i < len; ++ i ) {
		Complex c ( C[i] ), d ( D[i] ), e ( E[i] ), f ( F[i] );
		C[i] = c * e, D[i] = c * f + d * e, E[i] = d * f;
		P[i] = C[i] + Complex ( 0, 1 ) * E[i];
	}
	FFT ( len, D, -1 ), FFT ( len, P, -1 );
	for ( int i = 0; i < n + m - 1; ++ i ) {
		int c = ( LL ( P[i].x + 0.5 ) % p + p ) % p, d = ( LL ( D[i].x + 0.5 ) % p + p ) % p, e = ( LL ( P[i].y + 0.5 ) % p + p ) % p;
		wint ( ( c + ( 1ll << 15 ) % p * d % p + ( 1ll << 30 ) % p * e % p ) % p );
		putchar ( i + 1 == n + m - 1 ? '\n' : ' ' );
	}
	return 0;
}

ω 「CF 623E」Transforming Sequence ω

ω 题意简述 ω

  link.

  求有多少个长度为 n 的序列 {an},满足 ak(0,2k)akj=1k1aj。这里的集合运算把 ak 作为了二进制集合。对 109+7 取模。

ω 数据规模 ω

  n1018,k[1,3×104]

ω Solution ω

  详见我的题解。题意描述略有不同,请自行理解。为什么我的 MTT 有八次变换啊……

  专门提起这道题的目的,除了让大家实战运用 MTT 之外,还想让大家感受倍增这种几乎能融入所有 OI 知识点的思想。

  本来还有多项式全家桶,但因为太懒所以烂尾咯 qwq。

ω 参考资料 ω

  按在文中第一次借鉴或引用的位置顺序排列:

posted @   Rainybunny  阅读(903)  评论(3编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示