【学习笔记】多项式乘法

article/2025/8/15 4:16:52

文章目录

我最初学习 FFT \textit{FFT} FFT 的资料是《小学生都能看懂的 FFT \textit{FFT} FFT。这当然可以助你快速起步。但说实在话,数学是很重要(也很有趣)的,你应该把复数当做需要掌握的知识点(而不是 FFT \textit{FFT} FFT 中的引理)。

还有 OI-wiki \text{OI-wiki} OI-wiki 应该也能提供不少帮助。我当年完全不知道有这玩意儿

前置知识:复数

引子:虚数

定义 i i i 为使得 i 2 = − 1 i^2=-1 i2=1 成立的值。一般我们把 k i ( k ∈ R ) ki\;(k\in\R) ki(kR) 叫做 虚数

这个 i i i 不是实数,因此也无法写成 0.142857 0.142857 0.142857 cos ⁡ ( Φ ) \cos(\Phi) cos(Φ) 等等。我们只能保留这个 i i i 。这就是一种记法而已。

不难发现,此时任意 x < 0 x<0 x<0 都可以定义 x = i − x \sqrt{x}=i\sqrt{-x} x =ix ,因此 i i i 使得所有数的平方根都是有定义的。甚至 i i i 也有平方根,这就会涉及到马上要讲的 复数

如果你对域有所了解:这其实就是 C = R ( i ) \Complex=\Reals(i) C=R(i) 括域的结果。然而了解域之前应该会先学复数吧

定义

形如 z = x + y i z=x+yi z=x+yi 的数字,我们称之为 复数。其 实部 x x x虚部 y y y

我们可以用一个直角坐标系中的点 ( x , y ) (x,y) (x,y) 表示 z = x + y i z=x+yi z=x+yi 。发现和数轴类似,是一一对应关系。

由于是直角坐标系,也可以用幅角和模长的形式描述,即 z = r ⋅ ( cos ⁡ θ + i sin ⁡ θ ) z=r\cdot(\cos\theta+i\sin\theta) z=r(cosθ+isinθ),其中 θ \theta θ 是极角、 r r r 是长度(即 r = x 2 + y 2 r=\sqrt{x^2+y^2} r=x2+y2 ,为向量的模长)。

著名的欧拉公式 e x i = cos ⁡ x + i sin ⁡ x e^{xi}=\cos x+i\sin x exi=cosx+isinx 则为我们提供了另一个表示法: z = e θ i r z=e^{\theta i}r z=eθir 。我觉得欧拉公式是应该被了解的。

计算

那么 z 1 z 2 z_1z_2 z1z2 等于多少呢?设 z 1 = r 1 ⋅ ( cos ⁡ θ 1 + i sin ⁡ θ 1 ) , z 2 = r 2 ⋅ ( cos ⁡ θ 2 + i sin ⁡ θ 2 ) z_1=r_1\cdot(\cos\theta_1+i\sin\theta_1),\;z_2=r_2\cdot(\cos\theta_2+i\sin\theta_2) z1=r1(cosθ1+isinθ1),z2=r2(cosθ2+isinθ2),那么
z 1 z 2 = r 1 r 2 ⋅ [ ( cos ⁡ θ 1 cos ⁡ θ 1 − sin ⁡ θ 1 sin ⁡ θ 1 ) + i ( sin ⁡ θ 1 cos ⁡ θ 2 + cos ⁡ θ 1 sin ⁡ θ 2 ) ] = r 1 r 2 ⋅ [ cos ⁡ ( θ 1 + θ 2 ) + i sin ⁡ ( θ 1 + θ 2 ) ] \begin{align*} z_1 z_2 &=r_1 r_2\cdot[(\cos\theta_1\cos\theta_1-\sin\theta_1\sin\theta_1)+i(\sin\theta_1\cos\theta_2+\cos\theta_1\sin\theta_2)]\\ &=r_1 r_2\cdot[\cos(\theta_1+\theta_2)+i\sin(\theta_1+\theta_2)] \end{align*} z1z2=r1r2[(cosθ1cosθ1sinθ1sinθ1)+i(sinθ1cosθ2+cosθ1sinθ2)]=r1r2[cos(θ1+θ2)+isin(θ1+θ2)]

这告诉我们一个口诀:模长相乘,幅角相加

当然,用欧拉公式表示法甚至用不到和差角公式,可以直接证明。

性质

首先,由于 幅角相加,所以 x n = 1 x^n={1} xn=1 n n n 个取值。恰好把 2 π 2\pi 2π 的周角给 n n n 等分。

被我发现了,盗图狗!
上图为 n = 8 n=8 n=8 的情况。

ω n \omega_n ωn 表示所有 x x x 的解中,非零幅角最小的一个。也就是幅角为 2 π n 2\pi\over n n2π、模长为 1 1 1 的复数。这个 ω n \omega_n ωn 被称为 “单位复根”。

明显有这几个性质:

  • 可以同时扩倍,因为本质是比例关系。即

ω n k = ω n / 2 k / 2 \omega_n^k=\omega_{n/2}^{k/2} ωnk=ωn/2k/2

  • 在环上,对面同样也有一个点。即 n n n 为偶数时

ω n k = − ω n k + n 2 \omega_n^k=-\omega_n^{k+\frac{n}{2}} ωnk=ωnk+2n

  • 转一圈就会回家。根据定义

ω n n = 1 \omega_n^n=1 ωnn=1

有关多项式

如果已知 n n n 次多项式经过的 ( n + 1 ) (n{+}1) (n+1) 个点,我们就可以建立方程,唯一确定这个多项式。可参考 拉格朗日插值

为什么方程是线性无关的呢?如果你知道 f ( x 0 ) = f ( x ) m o d ( x − x 0 ) f(x_0)=f(x)\bmod(x{-}x_0) f(x0)=f(x)mod(xx0),并且你知道多项式是可以定义 gcd ⁡ \gcd gcd 的(也就是欧几里得环),那么 gcd ⁡ ( x − x 1 , x − x 2 ) = 1 ( x 1 ≠ x 2 ) \gcd(x{-}x_1,x{-}x_2)=1\;(x_1\ne x_2) gcd(xx1,xx2)=1(x1=x2) 说明模数两两互质,根据 CRT \textit{CRT} CRT 我们可以还原解。

点值多项式相乘

假设 f ( x ) f(x) f(x) 经过 ( x 0 , a ) (x_0,a) (x0,a),而 g ( x ) g(x) g(x) 经过 ( x 0 , b ) (x_0,b) (x0,b),那么考虑 h ( x ) = f ( x ) g ( x ) h(x)=f(x)g(x) h(x)=f(x)g(x),发现 h ( x 0 ) = f ( x 0 ) g ( x 0 ) = a b h(x_0)=f(x_0)g(x_0)=ab h(x0)=f(x0)g(x0)=ab,也就是说, h ( x ) h(x) h(x) 经过 ( x 0 , a b ) (x_0,ab) (x0,ab)

所以 点值多项式相乘是 O ( n ) \mathcal O(n) O(n) 。在一些情况下,这启发我们直接维护若干点值(完全忘却多项式的系数表示法)。

但是 FFT \textit{FFT} FFT 能够使得任意 n n n 次多项式都能在 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的时间内完成点值与系数表示的转化,因此获得了最快的多项式乘法方案。

大整数乘法

将各位数字视为系数,最后求 f ( 10 ) f(10) f(10) 即可。

或者你可以这样说:本质都是卷积。

FFT \textit{FFT} FFT

全称是 快速傅里叶变换 Fast Fourier Transform \text{Fast Fourier Transform} Fast Fourier Transform),高斯在 1805 1805 1805 年(电脑问世之前)就发明了它。计算数学真恐怖。

但为什么叫傅里叶变换呢?这涉及到傅里叶级数
g ( t ) ∼ ∑ n = − ∞ + ∞ f n exp ⁡ ( 2 π i n t ) f n : = ∫ − ∞ + ∞ exp ⁡ ( − 2 π i n t ) g ( t ) d t g(t)\sim\sum_{n=-\infty}^{+\infty}f_n\exp(2\pi int)\\ f_n:=\int_{-\infty}^{+\infty}\exp(-2\pi int)g(t)\mathrm{d}t g(t)n=+fnexp(2πint)fn:=+exp(2πint)g(t)dt

这或许是看 3b1b \text{3b1b} 3b1b 的绝佳借口。尽管其事实上并不干涉我们理解傅里叶变换

因此我们对 k n ( 0 ⩽ k < n ) \frac{k}{n}\;(0\leqslant k<n) nk(0k<n) 做频率,提取傅里叶级数的系数,就得到离散傅里叶变换( Discrete Fourier Transform \text{Discrete Fourier Transform} Discrete Fourier Transform)。

离散傅里叶变换

通过计算 f ( ω n k ) ( 0 ⩽ k < n ) f(\omega_n^{\thinspace k})\;(0\leqslant k<n) f(ωnk)(0k<n),我们得到了 n n n 个点值。这就是 DFT \textit{DFT} DFT 正变换了。

我们可以把它轻易地变换回原系数。只需要以 f ( ω n k ) f(\omega_n^{\thinspace k}) f(ωnk) x k x^k xk 的系数(得到 n n n 次多项式)然后求 ω n − k \omega_n^{-k} ωnk 的点值得到
∑ j = 0 n − 1 f ( ω n j ) ( ω n − k ) j = ∑ j = 0 n − 1 ∑ t = 0 n − 1 a t ω n j t ( ω n − k ) j = ∑ t = 0 n − 1 a t ∑ j = 0 n − 1 ω n ( k − t ) j \begin{align*} \sum_{j=0}^{n-1}f(\omega_n^{\thinspace j})(\omega_n^{-k})^j &=\sum_{j=0}^{n-1}\sum_{t=0}^{n-1}a_t\omega_n^{\thinspace jt}(\omega_n^{-k})^j\\ &=\sum_{t=0}^{n-1}a_t\sum_{j=0}^{n-1}\omega_n^{(k-t)j} \end{align*} j=0n1f(ωnj)(ωnk)j=j=0n1t=0n1atωnjt(ωnk)j=t=0n1atj=0n1ωn(kt)j

后面那个关于 j j j 的求和,是等比数列求和。当 k = t k=t k=t 时公比为 1 1 1,显然和为 n n n 。若 k ≠ t k\ne t k=t,则该值为
ω n ( k − t ) n − ω n 0 ω n k − t − 1 = 1 − 1 ω n k − t − 1 = 0 {\omega_n^{(k-t)n}-\omega_n^{\thinspace 0}\over\omega_{n}^{k-t}-1}=\frac{1-1}{\omega_n^{k-t}-1}=0 ωnkt1ωn(kt)nωn0=ωnkt111=0

可以想到,这就是对 ∫ 0 1 exp ⁡ ( 2 π i n t ) d t = 0 \int_{0}^{1}\exp(2\pi int)\text dt=0 01exp(2πint)dt=0 的离散模拟(这个叫法不严谨)。

因此,通过第二次插值,我们就可以得到原系数了。这就是逆变换。

完整地重复一遍结论:设 f ( x ) = ∑ j = 0 n − 1 a j x j f(x)=\sum_{j=0}^{n-1}a_jx^j f(x)=j=0n1ajxj,记 g ( x ) = ∑ j = 0 n − 1 f ( ω n j ) x j g(x)=\sum_{j=0}^{n-1}f(\omega_n^{\thinspace j})x^j g(x)=j=0n1f(ωnj)xj,则
a k = n − 1 g ( ω n − k ) a_k=n^{-1}g(\omega_n^{-k}) ak=n1g(ωnk)

快速傅里叶变换


f ( x ) = ∑ i = 0 n − 1 a i x i f(x)=\sum_{i=0}^{n-1}a_ix^i f(x)=i=0n1aixi

其中 n n n 为偶数。令
f 1 ( x ) = a 0 + a 2 x + a 4 x 2 + ⋯ + a n − 2 x n 2 − 1 f 2 ( x ) = a 1 + a 3 x + a 5 x 2 + ⋯ + a n − 1 x n 2 − 1 f_1(x)=a_0+a_2x+a_4x^2+\cdots+a_{n-2}x^{\frac{n}{2}-1}\\ f_2(x)=a_1+a_3x+a_5x^2+\cdots+a_{n-1}x^{\frac{n}{2}-1} f1(x)=a0+a2x+a4x2++an2x2n1f2(x)=a1+a3x+a5x2++an1x2n1

那么
f ( x ) = f 1 ( x 2 ) + x f 2 ( x 2 ) f(x)=f_1(x^2)+xf_2(x^2) f(x)=f1(x2)+xf2(x2)

x j = ω n j x_j=\omega_n^{\thinspace j} xj=ωnj 代入其中:
f ( ω n j ) = f 1 ( ω n 2 j ) + ω n j ⋅ f 2 ( ω n 2 j ) = f 1 ( ω n / 2 j ) + ω n j ⋅ f 2 ( ω n / 2 j ) \begin{align*} f(\omega_n^j) &=f_1(\omega_n^{2j})+\omega_n^{\thinspace j}\cdot f_2(\omega_n^{2j})\\ &=f_1(\omega_{n/2}^{\thinspace j})+\omega_n^{\thinspace j}\cdot f_2(\omega_{n/2}^{\thinspace j}) \end{align*} f(ωnj)=f1(ωn2j)+ωnjf2(ωn2j)=f1(ωn/2j)+ωnjf2(ωn/2j)

同理可得
f ( ω n j + n / 2 ) = f 1 ( ω n 2 j + n ) + ω n j + n / 2 ⋅ f 2 ( ω n 2 j + n ) = f 1 ( ω n / 2 j ) − ω n j ⋅ f 2 ( ω n / 2 j ) \begin{aligned} f(\omega_n^{j+n/2})&=f_1(\omega_n^{2j+n})+\omega_n^{j+n/2}\cdot f_2(\omega_n^{2j+n})\\ &=f_1(\omega_{n/2}^{\thinspace j})-\omega_n^{\thinspace j}\cdot f_2(\omega_{n/2}^{\thinspace j}) \end{aligned} f(ωnj+n/2)=f1(ωn2j+n)+ωnj+n/2f2(ωn2j+n)=f1(ωn/2j)ωnjf2(ωn/2j)

所以只需求解 f 1 , f 2 f_1,f_2 f1,f2 ω n / 2 j \omega_{n/2}^{\thinspace j} ωn/2j 处点值。这可以递归。复杂度 T ( n ) = O ( n ) + 2 T ( n 2 ) = O ( n log ⁡ n ) T(n)=\mathcal O(n)+2T({n\over 2})=\mathcal O(n\log n) T(n)=O(n)+2T(2n)=O(nlogn)

代码实现

蝴蝶变换

递归是耗费颇多的。我们研究一下划分系数的规律,以此去掉递归。

第一次划分,如果 x x x 是奇数,那么就会 + n 2 +\frac{n}{2} +2n(分到右边)。然后 x x x 成为了它所在那半边的第 ⌊ x 2 ⌋ \lfloor{x\over 2}\rfloor 2x 个系数。

对于第二次划分,如果 ⌊ x 2 ⌋ \lfloor{\frac{x}{2}}\rfloor 2x 是奇数,那么就会 + n 4 +\frac{n}{4} +4n(分到右边)。然后 x x x 成为了 ⌊ x 4 ⌋ \lfloor{x\over 4}\rfloor 4x

一直到第 i i i 次划分,如果 ⌊ x 2 i − 1 ⌋ \lfloor{x\over 2^{i-1}}\rfloor 2i1x 是奇数,也就是二进制下第 i i i 位(从 1 1 1 开始编号)为 1 1 1,则走到的位置的二进制表示下第 ( n + 1 − i ) (n+1-i) (n+1i) 位为 1 1 1

所以 将原下标二进制表示翻转,就是新位置的二进制表示。注意到移位先于所有计算,因此可以先移位,然后自底向上逐层模拟递归回溯前的计算。

计算 ω n − x \omega_n^{-x} ωnx

由于 ω n − x = ω n n − x \omega_n^{-x}=\omega_n^{n-x} ωnx=ωnnx,那么我们先求出 ω n x \omega_{n}^{\thinspace x} ωnx 作为自变量的点值。最后需要的点值 b x b_x bx 对应的自变量是 ω n − x = ω n n − x \omega_{n}^{-x}=\omega_{n}^{n-x} ωnx=ωnnx,也就是说第 x x x 项应该放在第 ( n − x ) (n{-}x) (nx) 项处;直接将得到的点值数组的第 1 1 1 ( n − 1 ) (n{-}1) (n1) 位翻转即可。

注意:如果只用 sin ⁡ , cos ⁡ \sin,\cos sin,cos 或者 exp ⁡ \exp exp 计算 ω n 1 \omega_n^{\thinspace 1} ωn1 再自乘,速度较快;对于每一个都用三角函数求解,速度较慢,但是精度更高(事实上前者的精度损失也并不是很大)。

当然,因为我们已经把它预处理出来了,所以不需要上面所说的 r e v e r s e \tt reverse reverse 的技巧;直接用 ω n n − x \omega_{n}^{n-x} ωnnx 代入,可以省去翻转的过程。

代码壹号

解释一下:我们预处理了幅角为 2 π n \frac{2\pi}{n} n2π 的单位复根的幂,现在对于长度为 2 w 2w 2w 的区间,相当于需要求出幅角为 2 π 2 w \frac{2\pi}{2w} 2w2π 的单位复根。显然它就是 2 π n ⋅ n 2 w \frac{2\pi}{n}\cdot\frac{n}{2w} n2π2wn,也就是预处理数组中的第 n 2 w n\over 2w 2wn 位。

另外,这个代码不必背下来。因为后面会讲更好的写法。

using cplx = complex<double>; // in file <complex>
const double pi = acos(-1);
cplx omg[MAXN]; int went[MAXN];
/** @param opt 1 to DFT, -1 to IDFT */
void FFT(cplx a[], int n, int opt){const int N = 1<<n; // real lengthfor(int i=1; i!=N; ++i){went[i] = (went[i>>1]>>1)|((i&1)<<n>>1);if(i < went[i]) swap(a[i],a[went[i]]);}for(int i=1; i!=N; ++i){omg[i].real(cos(2*pi*i/N));omg[i].imag(sin(2*pi*i/N));}omg[0].imag(0), omg[0].real(1);for(int w=1; w!=N; w<<=1)for(cplx *p=a; p!=a+N; p+=(w<<1))for(int i=0; i<w; ++i){cplx t = omg[(N/(w<<1)*i*opt+N)%N];t = t*p[i+w], p[i+w] = p[i]-t, p[i] += t;}if(!(~opt)) for(int i=0; i!=N; ++i) a[i] /= N;
}

改进方案

精度提升

众所周知, d o u b l e \tt double double 的能力有限。哪怕是 l o n g d o u b l e \tt long\; double longdouble,也有力不从心的时候。问题在于 数值太大,而过程中不能对值进行任何操作。尤其是对 ( 1 0 9 + 7 ) (10^9{+}7) (109+7) 等大质数取模时,中途是不能取模的——毕竟涉及实数乘法。

如何防爆呢?核心肯定在于,减小结果的大小。要么减少数字个数,要么减小数字大小。对于第一个,考虑 K a r a t s u b a \tt Karatsuba Karatsuba 乘法,每次让数字个数变为 1 2 1\over 2 21,只能让结果变为 1 4 1\over 4 41 。好像没有更好的方法了。此路不通。

那么怎么将数字减小呢?显然减法不行,考虑除法。实数除法也不行——数字变小了,但是小数点后的位数变多了,仍然存在精度问题。看来只有一条路了——整数除法,即整除。对某一个数作除法,照样无益;必须对所有数都作相同的除法。

比如除数是 M M M 。那么一个数变为 ⌊ a i M ⌋ M + ( a i m o d M ) \lfloor{a_i\over M}\rfloor M+(a_i\bmod M) MaiM+(aimodM),为了方便,记为 v i M + r i v_iM+r_i viM+ri 。于是 a i b j = r i b j + ( v i b j ) M a_ib_j=r_ib_j+(v_ib_j)M aibj=ribj+(vibj)M 。发现这两项的贡献是独立的,可以单独计算,最后相加!形式化地,写出两个新的多项式
f l o w ( x ) = ∑ i = 0 n − 1 ( a i m o d M ) x i f h i g h ( x ) = ∑ i = 0 n − 1 ⌊ a i M ⌋ x i f_{low}(x)=\sum_{i=0}^{n-1}\left(a_i\bmod M\right)x^i\\ f_{high}(x)=\sum_{i=0}^{n-1}\left\lfloor\frac{a_i}{M}\right\rfloor x^i flow(x)=i=0n1(aimodM)xifhigh(x)=i=0n1Maixi

f ( x ) g ( x ) = f l o w ( x ) g ( x ) + M ⋅ f h i g h ( x ) g ( x ) f(x)g(x)=f_{low}(x)g(x)+M\cdot f_{high}(x)g(x) f(x)g(x)=flow(x)g(x)+Mfhigh(x)g(x) 。显然取 M = max ⁡ a i M=\sqrt{\max a_i} M=maxai 能够使得值下降的最快。如果对 g ( x ) g(x) g(x) 再做一次此等拆分,则每个值的结果都在 ( max ⁡ a i ) ⋅ n (\max a_i)\cdot n (maxai)n 以内,效果拔群!

另外说一句:一般都会对 f ( x ) , g ( x ) f(x),g(x) f(x),g(x) 同时作拆分,因为这样只需要 4 4 4 次正变换、 3 3 3 次逆变换;如果封装过度,每次都调用 multiplies(将会调用 2 2 2 次正变换、 1 1 1 次逆变换)就会变为 8 8 8 次正变换、 4 4 4 次逆变换,大大的不好!

常数优化:二合一

如果我们要计算两个 系数为实数(虽然也基本上不可能是复数)的多项式 A ( x ) , B ( x ) A(x),B(x) A(x),B(x) 的乘积,似乎要做两次正变换?

可以这样:令 P ( x ) = A ( x ) + i B ( x ) , Q ( x ) = A ( x ) − i B ( x ) P(x)=A(x)+iB(x),\;Q(x)=A(x)-iB(x) P(x)=A(x)+iB(x),Q(x)=A(x)iB(x),则只需要求出这两个的点值,相加除以二就是 A A A 的点值,相减除以 2 i 2i 2i 就是 B B B 的点值。

而二者的共轭关系,使得我们有 “二合一” 的方案。用 z ‾ \overline{z} z 表示 z z z 的共轭。
P ( ω n k ) = ∑ j = 0 n ( a j + i b j ) ⋅ ω n k j = ∑ j = 0 n ( a j − i b j ) ω n ( n − k ) j ‾ = Q ( ω n n − k ) ‾ P(\omega_n^k)=\sum_{j=0}^{n}(a_j+ib_j)\cdot \omega_{n}^{kj}\\ =\sum_{j=0}^{n}\overline{(a_j-ib_j)\omega_{n}^{(n-k)j}}=\overline{Q(\omega_{n}^{n-k})} P(ωnk)=j=0n(aj+ibj)ωnkj=j=0n(ajibj)ωn(nk)j=Q(ωnnk)

当然你觉得这个很离谱,怎么就能够 “二合一” 呢?事实上,复数就是二元组,你相当于把两个数组放在一起变换罢了。所以你其实也可以用复数实现 “二合一” 的任意其他变换,但是效率可能并无提升,因为复数加法相较于实数加法就已经是两倍常数了。只是 FFT \textit{FFT} FFT 中,本来就是复数运算,所以是真正的常数优化。

注意上面的过程中,假定了 ( a j + i b j ) (a_j+ib_j) (aj+ibj) 的共轭是 ( a j − i b j ) (a_j-ib_j) (ajibj),所以必须要 a j , b j ∈ R a_j,b_j\in\R aj,bjR

逆变换也可以 “二合一”,只要 原多项式系数是实数。假设我们已经知道 A ( ω j ) , B ( ω j ) A(\omega^j),B(\omega^j) A(ωj),B(ωj) 两个点值序列,则假设存在系数为复数的多项式 P ( x ) P(x) P(x) 使得其点值为
P ( ω j ) = A ( ω j ) + i B ( ω j ) P(\omega^j)=A(\omega^j)+iB(\omega^j) P(ωj)=A(ωj)+iB(ωj)

逆变换后,得到一个复系数多项式 P ( x ) P(x) P(x) 。若将 P ( x ) P(x) P(x) 的系数按照实部与虚部拆分,即 P ( x ) = F ( x ) + i ⋅ G ( x ) P(x)=F(x)+i\cdot G(x) P(x)=F(x)+iG(x),其中 F ( x ) , G ( x ) F(x),G(x) F(x),G(x) 系数均为实数,那么显然有 P ( ω j ) = F ( ω j ) + i G ( ω j ) P(\omega^j)=F(\omega^j)+iG(\omega^j) P(ωj)=F(ωj)+iG(ωj)

由于 A ( x ) , B ( x ) A(x),B(x) A(x),B(x) 系数均为实数,立刻可得 { F ( x ) = A ( x ) G ( x ) = B ( x ) \begin{cases}F(x)=A(x)\\G(x)=B(x)\end{cases} {F(x)=A(x)G(x)=B(x) 是一组解。而 F ( x ) , G ( x ) F(x),G(x) F(x),G(x) 是唯一的(因为它们代表着 P ( x ) P(x) P(x) 的系数拆分),所以这就是唯一解。所以 P ( x ) P(x) P(x) 的系数的实部是 A ( x ) A(x) A(x),而虚部是 B ( x ) B(x) B(x),搞定!

常数优化:不蝴蝶变换

求点值的本质是乘了一个矩阵
( ω n 0 × 0 ω n 1 × 0 ⋯ ω n n × 0 ω n 0 × 1 ω n 1 × 1 ⋯ ω n n × 1 ⋮ ⋮ ⋱ ⋮ ω n 0 × n ω n 1 × n ⋯ ω n n × n ) \begin{pmatrix} \omega_{n}^{0\times 0} & \omega_{n}^{1\times 0} & \cdots & \omega_{n}^{n\times 0}\\ \omega_{n}^{0\times 1} & \omega_{n}^{1\times 1} & \cdots & \omega_{n}^{n\times 1}\\ \vdots & \vdots & \ddots & \vdots\\ \omega_{n}^{0\times n} & \omega_{n}^{1\times n} & \cdots & \omega_{n}^{n\times n}\\ \end{pmatrix} ωn0×0ωn0×1ωn0×nωn1×0ωn1×1ωn1×nωnn×0ωnn×1ωnn×n

FFT \textit{FFT} FFT 将其拆解为两个线性变换:将 a i a_i ai 放到二进制位翻转后的 i i i 的位置,称为蝴蝶变换;将蝴蝶变换后的序列进行各种操作,我们姑且称之为 “傅外叶变换” 吧。

考虑将上面的矩阵 转置。由于上面的矩阵是沿对角线对称的,所以转置不带来任何变化。但是对应到代码中,我们可以先进行 “傅外叶变换” 的转置,再进行蝴蝶变换的转置。而蝴蝶变换是交换位置,是沿对角线对称的,也没有区别。

做完这样的正变换之后,再做傅里叶变换(普通版)就可以把序列变回去。此时我们会发现:做傅里叶变换前需要一次蝴蝶变换,而做完 “傅外叶变换” 后也有一次蝴蝶变换。这不就相当于啥也没做嘛!

于是蝴蝶变换被抛弃了。蝴蝶变换是 random-access \text{random-access} random-access,常数较大,去掉它是一个不错的优化!

最后提一句,关于矩阵转置。傅里叶变换是自底向上,行向量 ( p i , p i + L ) (p_i,p_{i+L}) (pi,pi+L) ( 1 1 ω i − ω i ) \begin{pmatrix}1 & 1 \\ \omega^{i} & -\omega^i\end{pmatrix} (1ωi1ωi),所以 “傅外叶变换” 是行向量 ( p i , p i + L ) (p_i,p_{i+L}) (pi,pi+L) ( 1 ω i 1 − ω i ) \begin{pmatrix} 1 & \omega^i \\ 1 & -\omega^i\end{pmatrix} (11ωiωi),自顶向下进行。

typedef complex<double> fushu;
fushu omg[MAXN<<1];
void prepare(int n){for(int i=0; i<n; ++i){omg[i].imag(sin(2*M_PI*i/n));omg[i].real(cos(2*M_PI*i/n));}
}
void FFT(fushu a[],int n){const fushu *end_a = a+n;for(int w=n>>1,x=1; w; w>>=1,x<<=1)for(fushu *p=a; p!=end_a; p+=(w<<1))for(int i=0; i<w; ++i){const fushu l = p[i], r = p[i+w];p[i] = l+r, p[i+w] = (l-r)*omg[x*i];}
}
void DFFT(fushu a[],int n){const fushu *end_a = a+n;for(int w=1,x=n>>1; x; w<<=1,x>>=1)for(fushu *p=a; p!=end_a; p+=(w<<1))for(int i=0; i<w; ++i){fushu t = p[i+w]*omg[x*i];p[i+w] = p[i]-t, p[i] += t;}std::reverse(a+1,a+n);for(int i=0; i<n; ++i) a[i] /= n;
}

提醒:如果混用

本来我这里写的是 “不能混用”,然后被 Q u a c k \sf Quack Quack 光速打脸了……握巢,这摸猛犸?

能不能混用 不做蝴蝶变换二合一 呢?可以,但是稍微麻烦一点点。注意到 不做蝴蝶变换 等价于多做了一次蝴蝶变换,所以此时 ω n x \omega_n^x ωnx 对应的点值实际上放在 rev ( x ) \text{rev}(x) rev(x) 的位置。而 二合一 需要我们找到 ω n n − x \omega_n^{n-x} ωnnx 的点值,也就是 rev ( n − x ) \text{rev}(n{\rm-}x) rev(nx),然后放到 rev ( x ) \text{rev}(x) rev(x) 去。怎么办?

别忘了 n = 2 k n=2^k n=2k,所以 n − x n{\rm-}x nx 其实就是 x x x 保留 lowbit \text{lowbit} lowbit,然后高位按位取反(假设是 k k k 位二进制数)。于是 rev ( n − x ) \text{rev}(n{\rm-}x) rev(nx) 其实就是 rev ( x ) \text{rev}(x) rev(x) 保留 highbit \text{highbit} highbit,然后低位按位取反。预处理 highbit \text{highbit} highbit 就可以做到了。

再探 FFT \textit{FFT} FFT

可参考论文《再探快速傅立叶变换》和 T L Y \sf TLY TLY 太阳神 的 《课余时间的思考》。

重新理解

循环卷积的本质是对 ( x n − 1 ) (x^n-1) (xn1) 取模。而
x n − 1 = ∏ j = 0 n − 1 ( x − ω n j ) x^n-1=\prod_{j=0}^{n-1}(x-\omega_n^{\thinspace j}) xn1=j=0n1(xωnj)

是其唯一分解。因此对 ( x − ω n j ) (x-\omega_n^{\thinspace j}) (xωnj) 分别取模,再做 CRT \textit{CRT} CRT 即可。也许你可以参考兔兔的博客。

Chirp-Z Transform \text{Chirp-Z Transform} Chirp-Z Transform

也被称为 Bluestain \text{Bluestain} Bluestain 算法。目标是计算 f ( x m ) ( m ∈ [ 0 , n ] ) f(x^m)\;(m\in[0,n]) f(xm)(m[0,n]) 。通过 m i = ( m + i 2 ) − ( m 2 ) − ( i 2 ) mi={m+i\choose 2}-{m\choose 2}-{i\choose 2} mi=(2m+i)(2m)(2i),可以构造成卷积形式:
f ( x m ) = ∑ i = 0 n − 1 x m i a i = ∑ i = 0 n − 1 x ( m + i 2 ) − ( m 2 ) − ( i 2 ) × a i = x − ( m 2 ) ∑ i = 0 n − 1 x − ( i 2 ) a i × x ( m + i 2 ) \begin{align*} f(x^m) &=\sum_{i=0}^{n-1}x^{mi}a_i\\ &=\sum_{i=0}^{n-1}x^{{m+i\choose 2}-{m\choose 2}-{i\choose 2}}\times a_i\\ &=x^{-{m\choose 2}}\sum_{i=0}^{n-1}x^{-{i\choose 2}}a_i\times x^{{m+i\choose 2}} \end{align*} f(xm)=i=0n1xmiai=i=0n1x(2m+i)(2m)(2i)×ai=x(2m)i=0n1x(2i)ai×x(2m+i)

那么,想要实现任意长度的循环卷积,只需要将 x = e 2 π n x=e^{2\pi\over n} x=en2π 代入。时间复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

NTT \textit{NTT} NTT

全称是 Number Theoretic Transform \text{Number Theoretic Transform} Number Theoretic Transform,数论变换。

它的快速变换和离散变换拥有相同的名字,好奇怪哦。

引子:原根

如果 g φ ( p ) ≡ 1 ( m o d p ) g^{\varphi(p)}\equiv 1\pmod{p} gφ(p)1(modp) 且最小的使得 g k ≡ 1 ( m o d p ) g^k\equiv 1\pmod{p} gk1(modp) 的正整数 k = φ ( p ) k=\varphi(p) k=φ(p),那么称 g g g p p p 的原根。

特别地,上文中的 k k k 被称为 ord \text{ord} ord),但这不是我们要讨论的内容。

欧拉证明了 2 , 4 , p k , 2 p k ( k ⩾ 1 ) 2,4,p^k,2p^k\;(k\geqslant 1) 2,4,pk,2pk(k1) 有原根,其中 p p p 是奇素数。

模仿 FFT \textit{FFT} FFT

只要满足单位根的若干形式,一切结论都仍然成立。而单位根的本质是 ω n n = 1 \omega_n^{\thinspace n}=1 ωnn=1,所以等比数列求和会得到 0 0 0

那么,有了原根 g g g,发现 g φ ( p ) ≡ 1 g^{\varphi(p)}\equiv 1 gφ(p)1,所以定义 ω n = g φ ( p ) n ( n ∣ φ ( p ) ) \omega_n=g^{\varphi(p)\over n}\;(n\mid\varphi(p)) ωn=gnφ(p)(nφ(p)),就可以完全像 FFT \textit{FFT} FFT 那样进行卷积了。而且所有运算都在整数域(取模的剩余系)下运算,无任何精度误差!

同时我们也发现一个 局限性 φ ( p ) \varphi(p) φ(p) 需要含有足够多的 2 2 2 的次幂,因为 n ∣ φ ( p ) n\mid\varphi(p) nφ(p) 时才有定义。最常见的是 p = 998244353 p=998244353 p=998244353,此时 2 23 ∣ φ ( p ) 2^{23}\mid\varphi(p) 223φ(p),基本上不会超!

代码实现被放在了多项式运算里,这里就不再贴出。

改进:任意模数

要是模数没有原根怎么办?注意到求出的系数不应超过 a 2 n a^2n a2n,因此另找 3 3 3 个较大模数,用中国剩余定理合并,模意义下也能得到正确结果。

合并的时候好像还会爆 l o n g l o n g \tt{long\;long} longlong,怎么办?

假设已知
{ x ≡ c 1 ( m o d m 1 ) x ≡ c 2 ( m o d m 2 ) x ≡ c 3 ( m o d m 3 ) \begin{cases} x\equiv c_1\pmod {m_1}\\ x\equiv c_2\pmod {m_2}\\ x\equiv c_3\pmod {m_3} \end{cases} xc1(modm1)xc2(modm2)xc3(modm3)

我们首先将前两者合并得到
x ≡ k ( m o d m 1 m 2 ) x\equiv k\pmod {m_1 m_2} xk(modm1m2)

于是我们假设 x = t m 1 m 2 + k x=t m_1 m_2 + k x=tm1m2+k,由第三个同余方程得 t m 1 m 2 + k ≡ c 3 ( m o d m 3 ) t m_1 m_2 + k \equiv c_3 \pmod {m_3} tm1m2+kc3(modm3) 。移项则有

t ≡ ( c 3 − k ) ⋅ m 1 − 1 ⋅ m 2 − 1 ( m o d m 3 ) t\equiv (c_3-k)\cdot m_1^{-1}\cdot m_2^{-1}\pmod {m_3} t(c3k)m11m21(modm3)

这些运算都是模 m 3 m_3 m3 的,可以轻松进行。求出 ( t m o d m 3 ) (t\bmod m_3) (tmodm3) 也就得到了 x ≡ t m 1 m 2 + k ( m o d m 1 m 2 m 3 ) x\equiv tm_1m_2+k\pmod{m_1m_2m_3} xtm1m2+k(modm1m2m3),此时对原模数取模即可。

CNTT \textit{CNTT} CNTT

全称是 Complex NTT \text{Complex NTT} Complex NTT,复数论变换。他可以部分弥补取模 ( 4 k − 1 ) (4k{-}1) (4k1) 型的质数时 NTT \textit{NTT} NTT 的无力(虽然 任意模数 干翻一切,但是它难打而且慢)。

考虑在某个 G F ( p 2 ) \mathbb{GF}(p^2) GF(p2) 中进行运算,因为此时原根的阶是 ( p 2 − 1 ) = ( p + 1 ) ( p − 1 ) (p^2{-}1)=(p{+}1)(p{-}1) (p21)=(p+1)(p1) 。如果 ( p + 1 ) (p{+}1) (p+1) 里含有足够多的 2 2 2 的幂次,就可以了。

注意到 p ≡ 3 ( m o d 4 ) p\equiv 3\pmod{4} p3(mod4) 在高斯整数环 G = Z ( i ) \Bbb G=\Z(i) G=Z(i) 中是素元,因此 p G p\Bbb G pG 是素理想,即得 Z p ( i ) = G / p G \Z_p(i)=\Bbb G/p\Bbb G Zp(i)=G/pG 是整环,结合有限性知其是域。所以在 Z p ( i ) \Z_p(i) Zp(i) 上进行运算即可。但找原根就只能查表了吧

其实也可以理解为,在多项式环 Z p [ x ] \Z_p[x] Zp[x] 上,对 ( x 2 + 1 ) (x^2{+}1) (x2+1) 取模。核心也在于 ( − 1 ) (-1) (1) 不是模 p p p 的二次剩余,因此 ( x 2 + 1 ) (x^2{+}1) (x2+1) 是欧几里得环 Z p [ x ] \Z_p[x] Zp[x] 上不可约元素,整环性即证。

定理:交换幺环 R R R DFT \textit{DFT} DFT 可逆的充要条件是 ( ω k − 1 ) ( 1 ⩽ k < φ ) (\omega^k-1)\;(1\leqslant k<\varphi) (ωk1)(1k<φ) 可逆,其中 φ \varphi φ 是原根 ω \omega ω 的阶。

解释:其充分性是显然的,必要性暂时不清楚,故留作习题。

64-bit \text{64-bit} 64-bit 整数的卷积

2 64 2^{64} 264 意义下做卷积。

定义环 R : = Z m o d 2 64 R:=\Z\bmod 2^{64} R:=Zmod264 R : = Z / 2 64 Z R:=\Z/2^{64}\Z R:=Z/264Z

将环 R R R 拓展到 T : = R [ ω ] m o d ( ω 2 + ω + 1 ) T:=R[\omega]\bmod (\omega^2{+}\omega{+}1) T:=R[ω]mod(ω2+ω+1) 。立方差公式不难验证 ω 3 = 1 \omega^3=1 ω3=1

现在考虑在 T [ x ] m o d ( x n − ω ) T[x]\bmod(x^n{-}\omega) T[x]mod(xnω) 中做卷积,其中 n n n 3 3 3 的幂。

y : = x m y:=x^m y:=xm 满足 m r = n mr=n mr=n m ⩾ r m\geqslant r mr 。则原多项式 ∑ a i x i \sum a_ix^i aixi 可以写为
∑ j = 0 r − 1 y j ∑ i = 0 m − 1 a j m + i x i \sum_{j=0}^{r-1}y^j\sum_{i=0}^{m-1}a_{jm+i}x^i j=0r1yji=0m1ajm+ixi

将其视为关于 y y y 的多项式,认为其系数是 T [ x ] m o d ( x 2 m + x m + 1 ) T[x]\bmod(x^{2m}{+}x^m{+}1) T[x]mod(x2m+xm+1),这样显然不会丢失信息。于是我们现在在做 T [ x ] [ y ] m o d ( x 2 m + x m + 1 ) m o d ( y r − ω ) T[x][y]\bmod(x^{2m}{+}x^m{+}1)\bmod(y^r{-}\omega) T[x][y]mod(x2m+xm+1)mod(yrω) 的卷积,因为 y r = x n y^r=x^n yr=xn

为什么要对 x x x 的多项式取模呢?因为
x 2 m + x m + 1 = ( x m − ω ) ( x m − ω 2 ) x^{2m}+x^m+1=(x^m-\omega)(x^m-\omega^2) x2m+xm+1=(xmω)(xmω2)

我们只需分别对二者取模,然后用 CRT \textit{CRT} CRT 合并即可。

CRT \textit{CRT} CRT 是简单的:设原本为 ( a + b x m ) (a{+}bx^m) (a+bxm),对前者、后者取模分别得到 f , g f,g f,g,有关系式
{ f = a − b ω g = a − b ω 2 ⟹ { a = ω f − g ω − 1 b = f − g ω 2 − ω \begin{cases} f=a-b\omega\\ g=a-b\omega^2 \end{cases} \implies \begin{cases} a={\omega f-g\over\omega-1}\\ b=\frac{f-g}{\omega^2-\omega} \end{cases} {f=ag=abω2{a=ω1ωfgb=ω2ωfg

Comment.  C F \rm CF CF 帖子中似乎给出了奇怪的计算式,我不太理解。

最重要的是:此时都有 x 3 m = ω 3 = 1 x^{3m}=\omega^3=1 x3m=ω3=1,即 x x x 3 m 3m 3m 次单位根!

所以对 y y y 的循环卷积可行。可惜我们在对 ( y r − ω ) (y^r{-}\omega) (yrω) 取模,因此要换元。

若现在对 ( x m − ω ) (x^m{-}\omega) (xmω) 取模,那么换元 z : = x 2 m r y z:=x^{2m\over r}y z:=xr2my z r = ω 2 y r z^r=\omega^2y^r zr=ω2yr,只需对 ( z r − 1 ) (z^r{-}1) (zr1) 取模即可。

若现在对 ( x m − ω 2 ) (x^m{-}\omega^2) (xmω2) 取模,则换元 z : = x m r y z:=x^{m\over r}y z:=xrmy 同理。这也是要求 m ⩾ r m\geqslant r mr 的原因。

于是我们有能力作 FFT \textit{FFT} FFT 了。但点值相乘是 T [ x ] m o d ( x m − ω ) T[x]\bmod(x^m{-}\omega) T[x]mod(xmω) 内的卷积。所以我们需要递归解决。

反正 ω 2 \omega^2 ω2 ω \omega ω 没有本质区别,或者你可以认为是共轭的关系。

分析复杂度。 FFT \textit{FFT} FFT 复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn),点值相乘复杂度为 2 r T ( m ) 2r\mathcal T(m) 2rT(m),取 m = r = n m=r=\sqrt{n} m=r=n T ( n ) = 2 n T ( n ) + O ( n log ⁡ n ) = O ( n log ⁡ n log ⁡ log ⁡ n ) \mathcal T(n)=2\sqrt{n}\mathcal T(\sqrt{n})+\mathcal O(n\log n)=\mathcal O(n\log n\log\log n) T(n)=2n T(n )+O(nlogn)=O(nlognloglogn)

Comment. 注意 n ⇝ n n\leadsto\sqrt{n} nn 的递归会使得 log ⁡ n \log n logn 除以 2 2 2,因此刚好和递归系数中的 2 2 2 抵消。由递归层数 log ⁡ log ⁡ n \log\log n loglogn 即得。

Remark. 我们其实可以在任意代数结构上做卷积,但我还没看懂,因此只能贴个链接。


http://chatgpt.dhexx.cn/article/pSvYcFch.shtml

相关文章

多项式除法

多项式除法 应用场景 多项式的因式分解 使用 先试出有理根 r 多项式对线性因子 x - r 做多项式除法&#xff0c;逐步降低次数。 整除 : 结果就是商与被除数的乘积不整除 : 结果是商与余数/被除数的和 只到二次多项式&#xff0c;再利用十字相乘法或求根公式&#xff0c;即…

C语言 多项式乘法 算法

多项式乘法 什么是多项式&#xff1f; 由若干个单项式相加组成的代数式叫做多项式&#xff08;若有减法&#xff1a;减一个数等于加上它的相反数&#xff09;。 多项式中的每个单项式叫做多项式的项&#xff0c;这些单项式中的最高项次数&#xff0c;就是这个多项式的次数。 多…

多项式乘法入门

多项式乘法入门 By SemiWaker 这是一篇蒟蒻对FFT、DFT、CZT、NTT的弱鸡理解 多项式 a0xa1x1a2x2⋯an−1xn−1 上面的这个形式叫做多项式。 系数&#xff1a; a0..n−1 项&#xff1a; aixi 界&#xff1a;n 为了方便我们系数序列就可以表示多项式。 线性卷积 AB∑i02n−2(∑…

一元多项式的乘法与加法运算

题目要求 设计函数分别求两个一元多项式的乘积与和。 输入格式: 输入分2行&#xff0c;每行分别先给出多项式非零项的个数&#xff0c;再以指数递降方式输入一个多项式非零项系数和指数&#xff08;绝对值均为不超过1000的整数&#xff09;。数字间以空格分隔。 输出格式: 输…

多项式乘法运算初级版

快速傅里叶变换在信息学竞赛中主要用于求卷积&#xff0c;或者说多项式乘法。我们知道&#xff0c;多项式乘法的普通算法时间复杂度 是&#xff0c;通过快速傅里叶变换可以使时间降为&#xff0c;那么接下来会详细介绍快速傅里叶变换的原理。 首先来介绍多项式的两种表示方法&…

FFT与多项式乘法

网上关于FFT在信号处理中应用的文章并不少&#xff0c;这里尽量少说废话&#xff0c;直接说如何用FFT实现多项式乘法。 多项式乘法&#xff0c;通常是用系数乘积的方式完成&#xff0c;这样的时间复杂度是O(n^2) n为多项式项数。系数乘法可以满足大多数的乘法需求&#xff0c;然…

多项式乘法

实验题目&#xff1a;多项式乘法问题 实验内容与要求 一元稀疏多项式简单计算器的基本功能是&#xff1a; &#xff08;1)输入并建立多项式。&#xff1b; &#xff08;2&#xff09;输出多项式&#xff0c;输出形式为整数序列&#xff1a;n,c1,e1,c2,e2,…,cn,en,其中n是多项…

多项式乘法运算终极版

在上一篇文章中 http://blog.csdn.net/acdreamers/article/details/39005227 介绍了用快速傅里叶变 换来求多项式的乘法。可以发现它是利用了单位复根的特殊性质&#xff0c;大大减少了运算&#xff0c;但是这种做法是对复数系数的矩阵 加以处理&#xff0c;每个复数系数的实…

多项式乘法(FFT)

1 前言 作为一名OI选手&#xff0c;至今未写过fft相关的博客&#xff0c;真是一大遗憾&#xff0c;这也导致我并没有真正推过fft的所有式子 这一篇fft的博客我将详细介绍多项式乘法&#xff0c;易于理解&#xff0c;主要是为了等我啥时候忘了回来看&#xff0c;当然&#xff0…

分治算法-03多项式乘法问题

多项式乘法 简介 多项式的运算表示是一个很常见的算法问题。 问题描述 给予两个多项式A(x)与B(x)&#xff0c;得出C(x)A(x)B(x)。例如&#xff0c;A(x)32x3x24x3&#xff0c;B(x)2x2&#xff0c;C(x)64x9x210x33x44x^5。 问题分析 一般情况下&#xff0c;使用系数表示多项式&a…

【数据结构】——多项式乘法

题目要求 从字符文件输入两个多项式的非零系数及对应的指数&#xff0c;建立多项式的链式存储结构&#xff0c;计算这两个多项式的乘积&#xff0c;输出乘积多项式的全部非零系数及对应的指数到另一字符文件中。 算法原理 两个多项式的乘法&#xff0c;可以借助两个多项式的…

多项式乘法(FFT)详解

本文只探讨多项式乘法(FFT)在信息学中的应用 如有错误或不明欢迎指出或提问&#xff0c;在此不胜感激 多项式 1. 系数表示法 一般应用最广泛的表示方式 用A(x)表示一个x-1次多项式&#xff0c;a[i]为 xi x i 的系数&#xff0c;则A(x) ∑n−10 ∑ 0 n − 1 a[i] * xi x i…

2.2 多项式乘法与加法运算(线性结构,C)

多项式乘法与加法运算 设计函数分别求两个一元多项式的乘积与和题意理解题意理解和积 求解思路多项式表示两种表示方式在事先已经知道具体多少项的时候&#xff0c;本题较好的实现方法&#xff1a;动态数组链表表示多项式的方法 程序框架如何读入多项式读入多项式的完整程序 加…

多项式乘法问题

多项式乘法问题 实验目的&#xff1a;设计一个一元稀疏多项式简单计算器。 实验内容与要求&#xff1a; 一元稀疏多项式简单计算器的基本功能是&#xff1a; &#xff08;1&#xff09;输入并建立多项式&#xff1b; &#xff08;2&#xff09;输出多项式&#xff0c;序列…

网络协议之视频直播核心技术讲解

网络视频直播存在已有很长一段时间&#xff0c;随着移动上下行带宽提升及资费的下调&#xff0c;视频直播被赋予了更多娱乐和社交的属性&#xff0c;人们享受随时随地进行直播和观看&#xff0c;直播的打开时间和延迟变成了影响产品功能发展重要指标。 那么&#xff0c;问题来了…

直播软件搭建技术原理:CDN 与直播

直播软件搭建技术原理&#xff1a;CDN 与直播 很多直播都是基于 CDN 来实现的。而通过声网的服务&#xff0c;或基于声网SDK与 CDN 结合&#xff0c;还可以实现在直播中的连麦互动、白板同步等强调实时性的场景。本文源自社区投稿&#xff0c;介绍了该场景下的一些基础知识。如…

暑期实习+秋招面经合集(更新ing)

大纲 开篇 自我介绍 &#xff1a;面试官你好&#xff0c;我叫林飞武&#xff0c;是一名通信工程大三学生&#xff0c;对计算机专业有着浓厚兴趣并且未来有志于在互联网的测试领域有深入发展。全套学习了计算机专业的专业课&#xff0c;计算机网络&#xff0c;操作系统等&#…

DNSPod十问王征:为什么你的网站无人问津?

&#x1f4e2; DNSPod十八周年庆将至&#xff0c;下期十问交给你来提问——《你&#xff0c;十问DNSPod》&#xff01;评论区留下你想问DNSPod团队的问题&#xff0c;一旦提问被选中&#xff0c;将得到十八周年纪念T恤&#xff01;详情请移步至DNSPod公众号十八周年庆推送。 广…

阿里云ACP认证考试笔记

课件&#xff1a;https://gitee.com/HanFerm/technical-documentation/tree/master/阿里云acp教材 本文档为公开内容 一、ACP是干嘛的 内容范围&#xff1a; 历史 二、阿里云综述 技术架构 优势 三、弹性计算 ECS ECS的组成与功能 ECS是由多个并列又相互关联的产品概念组成…

在互联网上,没有人知道你是一条狗?

1993 年&#xff0c;《纽约客》&#xff08;The New Yorker&#xff09;杂志刊登一则由彼得施泰纳&#xff08;Peter Steiner&#xff09;创作的漫画&#xff1a;标题是【On the Internet, nobody knows you’re a dog.】 这则漫画中有两只狗&#xff1a;一只黑狗站在电脑椅上…