• Algorithm Review 6


    多项式

    拉格朗日插值

    • 已知最高次数不超过 n − 1 n - 1 n1 的多项式在平面上的 n n n 个点 ( x 1 , y 1 ) , ( x 2 , y 2 ) , … , ( x n , y n ) (x_1,y_1),(x_2,y_2),\dots,(x_n,y_n) (x1,y1),(x2,y2),,(xn,yn),满足 ∀ 1 ≤ i < j ≤ n , x i ≠ x j \forall 1 \le i < j \le n, x_i\not=x_j ∀1i<jn,xi=xj,则可还原出多项式:
      f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j f(x) = \sum\limits_{i = 1}^{n}y_i\prod\limits_{j \not=i} \frac{x - x_j}{x_i - x_j} f(x)=i=1nyij=ixixjxxj
    • x i = i x_i = i xi=i,且 x > n x > n x>n,则该式可简化为:
      f ( x ) = ∏ i = 1 n ( x − i ) ( ∑ i = 1 n ( − 1 ) n − i y i ( x − i ) ( i − 1 ) ! ( n − i ) ! ) f(x) = \prod\limits_{i = 1}^{n}(x - i)\left(\sum\limits_{i = 1}^{n}\frac{(-1)^{n-i}y_i}{(x-i)(i-1)!(n - i)!} \right) f(x)=i=1n(xi)(i=1n(xi)(i1)!(ni)!(1)niyi)
    • 预处理相关逆元和前缀积,即可 O ( n ) \mathcal O(n) O(n) 计算。
    • 若已知二元多项式在空间内的 n m nm nm 个点 ( x 11 , y 11 , z 11 ) , … , ( x n m , y n m , z n m ) (x_{11},y_{11},z_{11}),\dots,(x_{nm},y_{nm},z_{nm}) (x11,y11,z11),,(xnm,ynm,znm),满足任意两点横纵坐标至少一个不相等,且该多项式中 x x x 的最高次数不超过 n − 1 n - 1 n1 y y y 的最高次数不超过 m − 1 m - 1 m1,则可还原出多项式:
      f ( x , y ) = ∑ i = 1 n ∑ j = 1 m z i j ∏ k ≠ i x − x k j x i j − x k j ∏ l ≠ j x − x i l x i j − x i l f(x,y) = \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{m}z_{ij}\prod\limits_{k\not=i}\frac{x-x_{kj}}{x_{ij}-x_{kj}}\prod\limits_{l\not = j}\frac{x-x_{il}}{x_{ij}-x_{il}} f(x,y)=i=1nj=1mzijk=ixijxkjxxkjl=jxijxilxxil

    FFT

    • 即快速傅里叶变换。
    • n n n 次单位根 ω n k = cos ⁡ 2 π k n + i sin ⁡ 2 π k n \omega_{n}^k = \cos\frac{2\pi k}{n} + i\sin\frac{2\pi k}{n} ωnk=cosn2πk+isinn2πk
    • 将多项式 A , B A,B A,B 的项数补到 2 的整数次幂,设项数为 n n n,采用分治法快速求出 A , B A,B A,B 代入 ω n 0 , ω n 1 , … , ω n n − 1 \omega_{n}^0, \omega^1_n, \dots, \omega^{n - 1}_{n} ωn0,ωn1,,ωnn1 的点值,将点值相乘再通过类似的过程还原回多项式,即可快速求出多项式 A × B A \times B A×B,时间复杂度 O ( n log ⁡ n ) \mathcal O(n \log n) O(nlogn)

    DFT

    • 即离散傅里叶变换。
    • 对于多项式
      A ( x ) = ∑ k = 0 n − 1 a k x k = ∑ k = 0 n 2 − 1 ( a 2 k x 2 k + a 2 k + 1 x 2 k + 1 ) = ∑ k = 0 n 2 − 1 a 2 k ( x 2 ) k + x ∑ k = 0 n 2 − 1 a 2 k + 1 ( x 2 ) k A(x) = \sum\limits_{k = 0}^{n - 1}a_k x^k = \sum \limits_{k = 0}^{\frac{n}{2} - 1}(a_{2k}x^{2k} + a_{2k + 1}x^{2k + 1}) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}(x^{2})^k + x\sum\limits_{k = 0}^{\frac{n}{2} - 1}a_{2k+1}(x^2)^k A(x)=k=0n1akxk=k=02n1(a2kx2k+a2k+1x2k+1)=k=02n1a2k(x2)k+xk=02n1a2k+1(x2)k
    • A 1 ( x ) = ∑ k = 0 n 2 − 1 a 2 k x k , A 2 ( x ) = ∑ i = 0 n 2 − 1 a 2 k + 1 x k A_1(x) = \sum \limits_{k = 0}^{\frac{n}{2} - 1}a_{2k}x^k, A_2(x) = \sum\limits_{i = 0}^{\frac{n}{2}-1}a_{2k + 1}x^k A1(x)=k=02n1a2kxk,A2(x)=i=02n1a2k+1xk,若已求得 A 1 ( ω n 2 k ) , A 2 ( ω n 2 k ) , 0 ≤ k ≤ n 2 − 1 A_1(\omega_\frac{n}{2}^k), A_2(\omega^{k}_{\frac{n}{2}}), 0 \le k \le \frac{n}{2} - 1 A1(ω2nk),A2(ω2nk),0k2n1,则
      A ( ω n k ) = A 1 ( ω n 2 k ) + w n k A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) + w n k A 2 ( ω n 2 k ) A ( ω n k + n 2 ) = A 1 ( ω n 2 k ) − w n k + n 2 A 2 ( w n 2 k ) = A 1 ( ω n 2 k ) − w n k A 2 ( ω n 2 k ) A(\omega_n^k) = A_1(\omega^{2k}_{n}) + w_{n}^{k}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) + w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(\omega_n^{k + \frac{n}{2}}) = A_1(\omega^{2k}_{n}) - w_{n}^{k+\frac{n}{2}}A_2(w_{n}^{2k}) = A_1(\omega^{k}_{\frac{n}{2}}) -w_{n}^{k}A_2(\omega^{k}_{\frac{n}{2}}) \\ A(ωnk)=A1(ωn2k)+wnkA2(wn2k)=A1(ω2nk)+wnkA2(ω2nk)A(ωnk+2n)=A1(ωn2k)wnk+2nA2(wn2k)=A1(ω2nk)wnkA2(ω2nk)
    • 不断分治下去,此时我们便求得了 A ( ω n k ) , 0 ≤ k ≤ n − 1 A(\omega^{k}_n), 0 \le k \le n - 1 A(ωnk),0kn1

    IDFT

    • 即逆离散傅里叶变换,由之前的叙述我们有:
      [ ( ω n 0 ) 0 ( ω n 0 ) 1 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 ⋯ ( ω n n − 1 ) n − 1 ] [ a 0 a 1 ⋮ a n − 1 ] = [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[
      (ωn0)0(ωn0)1(ωn0)n1(ωn1)0(ωn1)1(ωn1)n1(ωnn1)0(ωnn1)1(ωnn1)n1" role="presentation" style="position: relative;">(ωn0)0(ωn0)1(ωn0)n1(ωn1)0(ωn1)1(ωn1)n1(ωnn1)0(ωnn1)1(ωnn1)n1
      \right] \left[
      a0a1an1" role="presentation" style="position: relative;">a0a1an1
      \right] = \left[
      A(ωn0)A(ωn1)A(ωnn1)" role="presentation" style="position: relative;">A(ωn0)A(ωn1)A(ωnn1)
      \right]
      (ωn0)0(ωn1)0(ωnn1)0(ωn0)1(ωn1)1(ωnn1)1(ωn0)n1(ωn1)n1(ωnn1)n1 a0a1an1 = A(ωn0)A(ωn1)A(ωnn1)
    • 对矩阵求逆,可得:
      [ a 0 a 1 ⋮ a n − 1 ] = 1 n [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) ) 0 ( ω n − 1 ) 1 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[
      a0a1an1" role="presentation" style="position: relative;">a0a1an1
      \right] = \frac{1}{n} \left[
      (ωn0)0(ωn0)1(ωn0)n1(ωn1))0(ωn1)1(ωn1)n1(ωn(n1))0(ωn(n1))1(ωn(n1))n1" role="presentation" style="position: relative;">(ωn0)0(ωn0)1(ωn0)n1(ωn1))0(ωn1)1(ωn1)n1(ωn(n1))0(ωn(n1))1(ωn(n1))n1
      \right] \left[
      A(ωn0)A(ωn1)A(ωnn1)" role="presentation" style="position: relative;">A(ωn0)A(ωn1)A(ωnn1)
      \right]
      a0a1an1 =n1 (ωn0)0(ωn1))0(ωn(n1))0(ωn0)1(ωn1)1(ωn(n1))1(ωn0)n1(ωn1)n1(ωn(n1))n1 A(ωn0)A(ωn1)A(ωnn1)
    • 不难发现,将 ω n k \omega_n^k ωnk 替换成 ω n − k \omega_n^{-k} ωnk 再做一遍 DFT,最后将结果除以 n n n 即可实现 IDFT

    位逆序变换

    • 为了减小递归实现带来的常数,我们考虑直接得到递归最底层的排列顺序,再逐层向上合并。
    • 不难证明,设 n = 2 m n = 2^m n=2m,则系数 a i a_i ai 递归到最底层时的下标恰好为 i i i m m m 位二进制表示下的对称翻转,我们称这个变换为位逆序变换(蝴蝶变换)
    • r e v [ i ] rev[i] rev[i] 表示系数 a i a_i ai 递归到最底层时的下标,则显然有递推式
    	int m = 0, _n = na + nb; //相乘的两个多项式最高次数分别为 na,nb
    	for (n = 1; n <= _n; n <<= 1)
    		++m;
    	for (int i = 1; i < n; ++i)
    		rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (m - 1));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 最终我们得到了 FFT 的迭代实现。
    typedef long double ld;
    typedef complex<ld> com;
    const ld pi = acos(-1.0);
    
    inline void DFT(vector<com> &a, int opt)
    {
    	int n = a.size();
    	for (int i = 0; i < n; ++i)
    		if (i < rev[i])
    			std::swap(a[i], a[rev[i]]);
    	for (int k = 1; k < n; k <<= 1)
    	{
    		com w(cos(pi / k), opt * sin(pi / k));;
    		for (int i = 0; i < n; i += k << 1)
    		{
    			com res(1.0, 0.0);
    			for (int j = 0; j < k; ++j)
    			{
    				com u = a[i + j],
    					v = res * a[i + j + k];
    				a[i + j] = u + v;
    				a[i + j + k] = u - v;
    				res = res * w;
    			}
    		}
    	}
    	if (opt == -1)
    	{
    		for (int i = 0; i < n; ++i)
    			a[i] /= n;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    NTT

    • 即快速数论变换。
    • 在模质数 P P P 意义下,原根 g g g 具有单位根的性质:
      ( g k ) P − 1 ≡ 1 ( m o d    P ) , g P − 1 2 ≡ − 1 ( m o d    P ) (g^k)^{P - 1} \equiv 1(\mod P), g^{\frac{P - 1}{2}}\equiv -1(\mod P) (gk)P11(modP),g2P11(modP)
    • P = 2 x a + 1 P = 2^xa +1 P=2xa+1,设 n = 2 m n = 2^m n=2m,用 g 2 x − m a g^{2^{x - m}a} g2xma 替换 ω n \omega_n ωn 即可实现 NTT
    • 常见的 P P P 有:
      P = 1004535809 = 479 × 2 21 + 1 , g = 3 P = 998244353 = 7 × 17 × 2 23 + 1 , g = 3 P = 1004535809 = 479 \times 2 ^ {21} + 1, g = 3 \\P = 998244353 = 7 \times 17 \times 2^{23} + 1, g = 3 P=1004535809=479×221+1,g=3P=998244353=7×17×223+1,g=3
    • 实现时可以预处理 g 2 x − m a g^{2^{x - m}a} g2xma 的幂次,减小常数。
    const int mod = 998244353;
    const int inv3 = 332748118;
    
    inline void DFT(vector<int> &a, int opt)
    {
    	int n = a.size(), g = opt == 1 ? 3 : inv3;
    	for (int i = 0; i < n; ++i)
    		if (i < rev[i])
    			std::swap(a[i], a[rev[i]]);
    	for (int k = 1; k < n; k <<= 1)
    	{
    		int w = quick_pow(g, (mod - 1) / (k << 1));
    		tw[0] = 1;
    		for (int j = 1; j < k; ++j)
    			tw[j] = 1ll * tw[j - 1] * w % mod;
    		for (int i = 0; i < n; i += k << 1)
    		{
    			for (int j = 0; j < k; ++j)
    			{
    				int u = a[i + j],
    					v = 1ll * tw[j] * a[i + j + k] % mod;
    				add(a[i + j] = u, v);
    				dec(a[i + j + k] = u, v);
    			}
    		}
    	}
    	if (opt == -1)
    	{
    		int inv_n = quick_pow(n, mod - 2); 
    		for (int i = 0; i < n; ++i)
    			a[i] = 1ll * a[i] * inv_n % mod;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
  • 相关阅读:
    嵌入式 Linux LED 驱动开发实验学习
    设置渐变边框色
    【AI】推理引擎中的模型小型化问题
    TensorRt推理部署优化方案及流程概述
    【2023.10版本】linux安装cuda和cudnn【已经解决】
    ffmpeg批量转码
    快速入手node.js
    MySQL 的执行原理(四)
    现代中央处理器(CPU)是怎样进行分支预测的?
    【单例模式和代理模式】
  • 原文地址:https://blog.csdn.net/bzjr_Log_x/article/details/126379530