多项式-快速傅里叶变换

解决问题

求解两个多项式相乘。
即对于两个 n 项多项式

f(x)=i=0n1ai×xig(x)=i=0n1bi×xi

求出

h(x)=i=0n1ci×xi

其中

ci=j=0iaj×bij

以上, n 为一个足够大的值。

前置知识

-复数

请自行学习复数相关知识。

- 单位根

将复平面单位圆 n 等分,规定 (1,0)0 次单位根,逆时针依次标号为 [0,n)

如图:

可以发现 ωnk 即为 ei2πn
由此可以证明以下=几条性质(当然也可以在单位圆上数形结合):

  • ωnn+k=ωnk

  • ωdndk=ωnk

  • ωnk+n2=ωnk

  • ωnk ωnk 共轭

单位根反演

1ni=0n1ωnv×i=[vmodn=0]

证:
vmodn=0 时 , v=kn,kZ

1ni=0n1ωnv×i=1ni=0n1ωnik×n=1ni=0n1ωn0=1ni=0n11=1n×n=1

vmodn0 时 , v=kn+r,k,rZr<n

1ni=0n1ωnv×i=1ni=0n1ωn(kn+r)×i=1ni=0n1ωnki×n+i×r=1ni=0n1ωni×r

又根据等比数列求和公式:

i=0n1a0×qi=a01qn1q

1ni=0n1ωni×r=1ni=0n1(ωnr)i=1n×1ωnnr1ωnr=1n×1ωn01ωnr=1n×0=0

两种情况综合,得证。

韦达定理

n 个点确定一个 n 项多项式。

过程

如果我们得知 n 个形如

(xi,f(xi))

的点值,便可以得出 h 的点值表示:

(xi,f(xi)×g(xi))

然后我们便可以确认 h 的系数。
将系数表示法转化为点值表示法的过程称为 DFT ,逆过程称为 IDFT

DFT

对于上述一个多项式,我们将它拆开来看:

f(x)=a0+a1x+a2x2+a3x3++an1xn1

将每一项按照奇偶性分离:

f(x)=a0+a2x2++an2xn2+a1x+a3x3++an1xn1

假定我们有另外两个多项式:

f1(x)=a0+a2x+a4x2++an2xn22f2(x)=a1+a3x+a5x2++an1xn22

可以发现:

f(x)=f1(x2)+xf2(x2)

好的,我们将它推成了很适合分治的形式,但这还不够。
接下来我们将 ωnk 代入看看能得到什么:
k<n2,代入 ωnkωnk+n2 得到:

f(ωnk)=f1(ωn2k)+ωnkf2(ωn2k)f(ωnk+n2)=f1(ωn2k+n)+ωnk+n2f2(ωn2k+n)=f1(ωn2k)ωnkf2ωn2k

我们惊喜地发现,只需要将 ωn2k 分别代入 f1f2,便可以得出 f 所有的点值。
每次问题的规模都被缩小一半,复杂度满足

T(n)=2T(n2)+O(n)

O(nlogn)
另外对于具体实现,发现我们需要保证 n2 的整次幂。

IDFT

对于 h 的其中一项 ci,有:

ci=j=0iaj×bij=j=0k=0aj×bk×[j+k=i]=j=0k=0aj×bk×[j+kimodn=0]=j=0k=0aj×bk×1nl=0ωn(j+ki)×l=j=0k=0aj×bk×1nl=0ωn(j+ki)×l

则:

nci=j=0k=0aj×bk×l=0ωnjl×ωnkl×ωnilnci=l=0ωnil×j=0ajωnjl×k=0bkωnklnci=l=0ωni×(l)f(ωnl)g(ωnl)nci=l=0h(ωnl)ωni×(l)

DFT 得到的 hg 的点值相乘作为系数,在分别代入 ωnk,得到的结果在除以 n,便得到了对应的系数。
如何代入 ωnk,详情见代码:


#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)

typedef std::complex<double> cp;
typedef long long ll;
const int _ = 4e6 + 7;
const double PI = std::acos(-1);

int n, m;
cp A[_], B[_], b[_];

void FFT(cp a[], int n, int opt) {
    if (n == 1) return; int mid = (n >> 1);
    lep(i, 0, mid - 1) b[i] = a[i << 1], b[i + mid] = a[i << 1 | 1];
    lep(i, 0, n - 1) a[i] = b[i];
    FFT(a, mid, opt), FFT(a + mid, mid, opt);
    cp wk = cp(1.0, 0.0), w1 = cp(std::cos(1.0 * PI / mid), opt * std::sin(1.0 * PI / mid));//共轭,虚部取负
    lep(i, 0, mid - 1) {
        b[i] = a[i] + wk * a[i + mid],
        b[i + mid] = a[i] - wk * a[i + mid];
        wk *= w1;
    }
    lep(i, 0, n - 1) a[i] = b[i];
}

int main() {
    std::ios::sync_with_stdio(false),
    std::cin.tie(nullptr), std::cout.tie(nullptr);
    std::cin >> n >> m;
    lep(i, 0, n) std::cin >> A[i];
    lep(i, 0, m) std::cin >> B[i];
    
    m = n + m, n = 1;
    while (n <= m) n <<= 1;
    
    FFT(A, n, 1), FFT(B, n, 1);
    lep(i, 0, n - 1) A[i] *= B[i];
    
    FFT(A, n, -1);
    lep(i, 0, m) std::cout << (int)(A[i].real() / n + 0.5) << ' ';
    return 0;
}

蝴蝶变换优化

上述是常见的 FFT 递归写法,但其实,我们可以将其写成常数更小的非递归写法。

对于一个 n 项多项式,递归过程中的系数变化如下:


s: a_0 a_1   a_2 a_3   a_4 a_5   a_6 a_7
1: 000 001   010 011   100 101   110 111
2: 000 010   100 110 | 001 011   101 111
3: 000 100 | 010 110 | 001 101 | 011 111
t: a_0 a_4   a_2 a_6   a_1 a_5   a_3 a_7

可以发现,递归分奇偶的过程就是将 aiaj 交换。
其中 i 的二进制表示为 j 的二进制表示的翻转(n 位表示)。

所以我们可以直接处理处每一个系数最后会递归到哪一个位置,然后从下至上递推答案。

定义 f[i]i 二进制表示翻转后的十进制表示,有递推式如下:

f[i]=(f[i>>1]>>1)((i&1)<<(l1))

(想一想,为什么)

迭代版代码如下:


void Init() {
    m = n + m, n = 1;
    while (n <= m) n <<= 1, ++l;
    lep(i, 1, n - 1) f[i] = (f[i >> 1] >> 1) | ((i & 1) << (l - 1));
}
void FFT(cp a[], int n, int opt) {
    lep(i, 1, n - 1) if (i < f[i]) std::swap(a[i], a[f[i]]);
    for (int mid = 1; mid < n; mid <<= 1) {
        for (int j = 0; j < n; j += (mid) << 1) {
            cp wk = cp(1, 0), w1 = cp(std::cos(PI / mid), opt * std::sin(PI / mid));
            lep(k, 0, mid - 1) {
                b[j + k] = a[j + k] + wk * a[j + k + mid],
                b[j + k + mid] = a[j + k] - wk * a[j + k + mid];
                wk *= w1;
            }
        }
        lep(i, 0, n - 1) a[i] = b[i];
    }
}

作者:qkhm

出处:https://www.cnblogs.com/qkhm/p/18691133/FFT

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   qkhm  阅读(33)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示