【FFT快速傅里叶变换】讲解

参考来源:
十分简明易懂的FFT(快速傅里叶变换)
小学生都能看懂的FFT!!!

一、FFT的介绍

  1. FFT是什么
    快速傅里叶变换(FFT)是一种能在 O ( n log ⁡ n ) O(n\log{n}) O(nlogn) 的时间内将一个多项式转换成它的点值表示的算法。

  2. FFT的作用
    快速计算多项式乘法(即卷积)
    (还可以用到字符串的模糊匹配)

    前置知识

  3. 多项式的表示
    【FFT快速傅里叶变换】讲解_第1张图片
    点值表示法:

  • 实际上就是把多项式看成一个函数,放到直角坐标系里。
  • 这条曲线上就有无数个点,取n个不同的x带入,会得到n个不同的y,他们在坐标系中,就是n个不同的在这条曲线上的点。
  • 也就是说,这n个点唯一确定这个多项式。 (为什么唯一确定呢?)
  • 因为:把这n个x,y带入多项式,得到n个式子,把这n个式子联立起来,得到一个有n条方程的n元方程组,就可以求得每一项的系数(高斯消元法)。这样就唯一确定了一个多项式。
  1. 多项式乘法
  • 系数表示的多项式乘法【 O ( n 2 ) O(n2) O(n2)】:枚举A(x)中的每一项,分别与B(x)中的每一项相乘,求得新的多项式C(x)。

  • 点值表示的多项式乘法【 O ( n ) O(n) O(n)】: C ( x i ) = A ( x i ) × B ( x i ) C(xi)=A(xi)×B(xi) C(xi)=A(xi)×B(xi)

  • 那么我们要计算多项式的乘法,就可以先将系数表示转换为点值表示,相乘以后,再将点值表示转换为系数表示即可。——这个转换过程就用到了FFT。

  • 朴素:系数转点值的算法叫DFT(离散傅里叶变换)

  • 朴素:点值转系数叫IDFT(离散傅里叶逆变换)

二、DFT(离散傅里叶变换:系数表示 —> 点值表示)

  1. 复数知识回顾
  • 复数可以看做复平面直角坐标系上的一个点(也可以是向量)。
    x轴就是实数集的坐标轴,y轴就是虚数单位i轴
  • 复数z的模定义为它到原点的距离,记为 ∣ z ∣ = ( a 2 + b 2 ) ∣z∣=\sqrt{(a^2 + b^2)} z=(a2+b2)
  • 复数z=a+bi的共轭复数为 a − b i a−bi abi(虚部取反)
  • 复数运算
    z 1 ​ + z 2 ​ = ( a + c ) + ( b + d ) i z1​+z2​=(a+c)+(b+d)i z1+z2=(a+c)+(b+d)i
    z 1 ​ z 2 ​ = ( a c − b d ) + ( a d + b c ) i z1​z2​=(ac−bd)+(ad+bc)i z1z2=(acbd)+(ad+bc)i
    ( a 1 ​ , θ 1 ​ ) ∗ ( a 2 ​ , θ 2 ​ ) = ( a 1 ​ a 2 ​ , θ 1 ​ + θ 2 ​ ) — — 模 长 相 乘 , 极 角 相 加 (a1​,θ1​)∗(a2​,θ2​)=(a1​a2​,θ1​+θ2​) —— 模长相乘,极角相加 (a1,θ1)(a2,θ2)=(a1a2,θ1+θ2)
  1. 离散傅里叶变换的思考
  • 从这里开始所有的n都默认为2的整数次幂(虽然我不知道有什么用,但是大家都这样写了emmm,好像是为了保证每次分治时,不会出现奇数的长度)。
  • 由上面的知识可知,系数表示 —> 点值表示,只需要带入n个x,求得n个点,就可以得到点值表示了。但是这n个点不是随意带入的,因为暴力计算 x k 0 , . . . , x k n − 1 x^0_k,... ,x^{n-1}_k xk0...xkn1 也很费时间。
  • 如果我们带入的这个x,能够使得 x k = ± 1 , ∀ k x^k=\pm1,\forall k xk=±1k ,那么我们就不用计算 x k n − 1 x^{n-1}_k xkn1了。
  • 显然,复平面直角坐标系的单位圆上的每一个点都可以做到。
  1. 单位根
  • 如果将单位圆等分为8等分,则橙色点即为n=8时要取的点,从(1,0)点开始,逆时针从0号开始标号,标到7号。
    【FFT快速傅里叶变换】讲解_第2张图片这个图是我偷的

  • 记编号为k的点代表的复数值为 ω n k ω^k_n ωnk​,那么由模长相乘,极角相加可知 ( ω n 1 ) k = ω n k (ω^1_n)^k=ω^k_n (ωn1)k=ωnk

  • 其中 ω n 1 ω^1_n ωn1称为n次单位根,而且每一个ω都可以求出为
    ω n k = cos ⁡ k n 2 π + i sin ⁡ k n 2 π ω^k_n=\cos{\frac{k}{n}2π}+i\sin{\frac{k}{n}2π} ωnk=cosnk2π+isinnk2π

  • 那么 ω n 0 , ω n 1 , . . . , ω n n − 1 ​ ω^0_n,ω^1_n,...,ω^{n−1}_n​ ωn0,ωn1,...,ωnn1即为我们要代入的 x 0 , x 1 , . . . , x n − 1 ​ x^0,x^1,...,x^{n−1}​ x0,x1,...,xn1

  • 单位根的性质

    1、 ω n k ​ = ω 2 n 2 k ​ ω_n^k​=ω_{2n}^{2k}​ ωnk=ω2n2k

    它们表示的点(或向量)表示的复数是相同的。

    2、 ω n k + n 2 ​ ​ = − ω n k ​ ω_n^{k+\frac{n}{2}}​​=−ω_n^k​ ωnk+2n=ωnk

    它们表示的点关于原点对称,所表示的复数实部相反,所表示的向量等大反向。

    3、 ω n 0 ​ = ω n n ​ ω_n^0​=ω_n^n​ ωn0=ωnn

证明:把系数带到三角函数表达式中即可证明。

三、FFT(快速傅里叶变换:系数表示 —> 点值表示)

DFT 分治 --> FFT
【FFT快速傅里叶变换】讲解_第3张图片【FFT快速傅里叶变换】讲解_第4张图片
那么如果可以求出 A 1 ​ ( ω n k ​ ) A1​(ω_n^k​) A1(ωnk) A 2 ( ω n k ) A2(ω^k_n) A2(ωnk)的值,我们就可以求出 A ( ω n k ​ ) A(ω_n^k​) A(ωnk) A ( ω n k + n 2 ) A(ω^{k + \frac{n}{2}}_n) A(ωnk+2n)的值( k < n 2 k< \frac{n}{2} k<2n),也就是知道 A ( ω n i ​ ) A(ω_n^i​) A(ωni) 0 < = i < n 0 <= i< n 0<=i<n)的值了。

  • 分治边界是n=1,直接return。

  • 分治的复杂度:
    T ( n ) = 2 T ( n 2 ) + O ( n ) = O ( n log ⁡ n ) T(n) = 2T(\frac{n}{2}) + O(n)= O(n\log{n}) T(n)=2T(2n)+O(n)=O(nlogn)

四、IFFT(快速傅里叶逆变换:点值表示 —> 系数表示)

【FFT快速傅里叶变换】讲解_第5张图片

  • 结论: 一个多项式在分治的过程中乘上单位根的共轭复数,分治完的每一项除以n即为原多项式的每一项系数。
  • 也就是说 FFT 和 IFFT 可以用几乎同样的方式一起求,只不过IFFT带入前,需要求一次倒数(即共轭复数),最后的系数还需要除以n,其他的求法都一样。

五、代码实现(主要看思路)

以下板子都是我抄的,没有测试过,看个思路就可以了。

1、递归版

#include
#define cp complex

cp omega(int n, int k)
{
    return cp(cos(2 * PI * k / n), sin(2 * PI * k / n));
}

void fft(cp  *a, int n, bool inv)
{
    if(n == 1)
        return;
    static cp buf[N];
    int m = n / 2;//mid
    for(int i = 0; i < m; i++)  //将每一项按照奇偶分为两组
    {
        buf[i] = a[2 * i];
        buf[i + m] = a[2 * i + 1];
    }
    for(int i = 0; i < n; i++)
        a[i] = buf[i];
    fft(a, m, inv); //递归处理两个子问题
    fft(a + m, m, inv);
    for(int i = 0; i < m; i++)  //枚举x,计算A(x)
    {
        cp x = omega(n, i);
        if(inv)
            x = conj(x);
        //conj是一个自带的求共轭复数的函数,精度较高。当复数模为1时,共轭复数等于倒数
        buf[i] = a[i] + x * a[i + m]; //根据之前推出的结论计算
        buf[i + m] = a[i] - x * a[i + m];
    }
    for(int i = 0; i < n; i++)
    {
        a[i] = buf[i];
        if(inv)
            a[i] = (int)(a[i]/n + 0.5);//注意精度
    }
}


//求a,b相乘
cp a[MAXN],b[MAXN];
int c[MAXN];
fft(a, n, 0), fft(b, n, 0);//0系数转点值
for(int i = 0; i < n; i++)
    a[i] *= b[i];
fft(a, n, 1);//1点值转系数
for(int i = 0; i < n; i++)
    c[i] = a[i];

2、非递归版(较快)
【FFT快速傅里叶变换】讲解_第6张图片

  • 可以发现每个位置分治后的最终位置为其二进制翻转后得到的位置。
  • 那么如何快速求一个数的二进制翻转呢?
  • 我们 可以用递归的思路来考虑。
  • 我们可以把一个二进制数看成两部分,它的前bit-1位是一部分,它的最后一位是一部分。一个数的二进制翻转就相当于是把它的最后一位当成首位,然后在后面接上它前bit-1为的二进制翻转。
  • 而且在这个循环中我们能保证,在计算“i”的二进制翻转之前1~i-1中的所有数的二进制翻转都已经完成。“i”的前bit-1位的数值其实就是i>>1的值,直接调用i>>1的二进制翻转的结果就相当于调用了“i”的前bit-1位二进制翻转的结果。【FFT快速傅里叶变换】讲解_第7张图片
  • 但是i>>1的翻转与“i”的前bit-1位的翻转是区别的!因为我们的二进制翻转始终以bit位为标准,所以i>>1会比“i”的前bit-1位多出一个前导零,而翻转之后就会多出一个“后缀零”,所以“i”的前bit-1位的翻转要去掉那个“后缀零”,也就是“rev[i>>1]>>1”。
    【FFT快速傅里叶变换】讲解_第8张图片
  • 因此,我们只要把末尾左移 ( b i t − 1 ) (bit-1) (bit1)位变成首位,再或上面得到的前 b i t − 1 bit-1 bit1位翻转并处理的结果: r e v [ i > > 1 ] > > 1 rev[i>>1]>>1 rev[i>>1]>>1就是我们要的答案了。

思路来源:补充——FFT中的二进制翻转问题

int bit = 0;
while((1 << bit) < n)
    bit++;
for(int i = 0; i < n; i++)
{
    rev[i] = (rev[i>>1]>>1) | ((i&1)<<(bit-1));
    if(i < rev[i])
        swap(a[i], a[ rev[i] ]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
}

  • 先把每个数放到最后的位置上,然后不断向上还原,同时求出点值表示。
  • 可以预处理 ω n k ω^k_n ωnk ω n − k ω^{−k}_n ωnk,分别存在omg和inv数组中。调用fft时,如果无需取倒数,则传入omg;如果需要取倒数,则传入inv。
cp a[N], b[N], omg[N], inv[N];

void init()
{
    for(int i = 0; i < n; i++)
    {
        omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
        inv[i] = conj(omg[i]);
    }
}
void fft(cp *a, cp *omg)
{
    int lim = 0;
    while((1 << lim) < n)
        lim++;
    for(int i = 0; i < n; i++)
    {
        int t = 0;
        for(int j = 0; j < lim; j++)
            if((i >> j) & 1)
                t |= (1 << (lim - j - 1));
        if(i < t)
            swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
    }
    static cp buf[N];
    for(int l = 2; l <= n; l *= 2)
    {
        int m = l / 2;
        for(int j = 0; j < n; j += l)
            for(int i = 0; i < m; i++)
            {
                buf[j + i] = a[j + i] + omg[n / l * i] * a[j + i + m];
                buf[j + i + m] = a[j + i] - omg[n / l * i] * a[j + i + m];
            }
        for(int j = 0; j < n; j++)
            a[j] = buf[j];
    }
}

3、蝴蝶操作

  • “蝴蝶操作”的目的是:丢掉buf数组。

  • buf的使用:
    a [ j + i ] = a [ j + i ] + o m g [ n / l ∗ i ] ∗ a [ j + i + m ] a[j + i] = a[j + i] + omg[n / l * i] * a[j + i + m] a[j+i]=a[j+i]+omg[n/li]a[j+i+m]
    a [ j + i + m ] = a [ j + i ] − o m g [ n / l ∗ i ] ∗ a [ j + i + m ] a[j + i + m] = a[j + i] - omg[n / l * i] * a[j + i + m] a[j+i+m]=a[j+i]omg[n/li]a[j+i+m]

    要求这两行不能互相影响,所以我们需要buf数组。

  • 原地进行:
    c p t = o m g [ n / l ∗ i ] ∗ a [ j + i + m ] cp t = omg[n / l * i] * a[j + i + m] cpt=omg[n/li]a[j+i+m]
    a [ j + i + m ] = a [ j + i ] − t a[j + i + m] = a[j + i] - t a[j+i+m]=a[j+i]t
    a [ j + i ] = a [ j + i ] + t a[j + i] = a[j + i] + t a[j+i]=a[j+i]+t

cp a[N], b[N], omg[N], inv[N];

void init(){
    for(int i = 0; i < n; i++){
        omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
        inv[i] = conj(omg[i]);
    }
}
void fft(cp *a, cp *omg){
    int lim = 0;
    while((1 << lim) < n) lim++;
    for(int i = 0; i < n; i++){
        int t = 0;
        for(int j = 0; j < lim; j++)
            if((i >> j) & 1) t |= (1 << (lim - j - 1));
        if(i < t) swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
    }
    for(int l = 2; l <= n; l *= 2){
    int m = l / 2;
    for(cp *p = a; p != a + n; p += l)
        for(int i = 0; i < m; i++){
            cp t = omg[n / l * i] * p[i + m];
            p[i + m] = p[i] - t;
            p[i] += t;
        }
    }
}

高精度使用

#include 
#include 
#include 
#include 
#include 
#define space putchar(' ')
#define enter putchar('\n')
using namespace std;
typedef long long ll;
template <class T>
void read(T &x)
{
    char c;
    bool op = 0;
    while(c = getchar(), c < '0' || c > '9')
        if(c == '-')
            op = 1;
    x = c - '0';
    while(c = getchar(), c >= '0' && c <= '9')
        x = x * 10 + c - '0';
    if(op)
        x = -x;
}
template <class T>
void write(T x)
{
    if(x < 0)
        putchar('-'), x = -x;
    if(x >= 10)
        write(x / 10);
    putchar('0' + x % 10);
}
const int N = 1000005;
const double PI = acos(-1);
typedef complex <double> cp;
char sa[N], sb[N];
int n = 1, lena, lenb, res[N];
cp a[N], b[N], omg[N], inv[N];
void init()
{
    for(int i = 0; i < n; i++)
    {
        omg[i] = cp(cos(2 * PI * i / n), sin(2 * PI * i / n));
        inv[i] = conj(omg[i]);
    }
}
void fft(cp *a, cp *omg)
{
    int lim = 0;
    while((1 << lim) < n)
        lim++;
    for(int i = 0; i < n; i++)
    {
        int t = 0;
        for(int j = 0; j < lim; j++)
            if((i >> j) & 1)
                t |= (1 << (lim - j - 1));
        if(i < t)
            swap(a[i], a[t]); // i < t 的限制使得每对点只被交换一次(否则交换两次相当于没交换)
    }
    for(int l = 2; l <= n; l *= 2)
    {
        int m = l / 2;
        for(cp *p = a; p != a + n; p += l)
            for(int i = 0; i < m; i++)
            {
                cp t = omg[n / l * i] * p[i + m];
                p[i + m] = p[i] - t;
                p[i] += t;
            }
    }
}
int main()
{
    scanf("%s%s", sa, sb);
    lena = strlen(sa), lenb = strlen(sb);
    while(n < lena + lenb)
        n *= 2;
    for(int i = 0; i < lena; i++)
        a[i].real(sa[lena - 1 - i] - '0');
    for(int i = 0; i < lenb; i++)
        b[i].real(sb[lenb - 1 - i] - '0');
    init();
    fft(a, omg);
    fft(b, omg);
    for(int i = 0; i < n; i++)
        a[i] *= b[i];
    fft(a, inv);
    for(int i = 0; i < n; i++)
    {
        res[i] += floor(a[i].real() / n + 0.5);
        res[i + 1] += res[i] / 10;
        res[i] %= 10;
    }
    for(int i = res[lena + lenb - 1] ? lena + lenb - 1: lena + lenb - 2; i >= 0; i--)
        putchar('0' + res[i]);
    enter;
    return 0;
}

六、模板

例题:A * B Problem Plus HDU - 1402 (大数高精度乘法)

Calculate A * B.

Input

Each line will contain two integers A and B. Process to end of file.

Note: the length of each integer will not exceed 50000.

Output

For each case, output A * B in one line.

Sample Input
1
2
1000
2

Sample Output
2
2000

AC代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define maxn (1<<16)
#define pi acos(-1)

using namespace std;

struct Complex
{
    double re, im;
    Complex(double r = 0.0, double i = 0.0)
    {
        re = r, im = i;
    }
    void print()
    {
        printf("%lf %lf\n", re, im);
    }
};

Complex operator +(const Complex&A, const Complex&B)
{
    return Complex(A.re + B.re, A.im + B.im);
}
Complex operator -(const Complex&A, const Complex&B)
{
    return Complex(A.re - B.re, A.im - B.im);
}
Complex operator *(const Complex&A, const Complex&B)
{
    return Complex(A.re * B.re - A.im * B.im, A.re * B.im + A.im * B.re);
}

//以每一位为系数, 那么多项式长度不超过50000
//对应的乘积的长度不会超过100000, 也就是不超过(1 << 17) = 131072
Complex a[maxn * 2], b[maxn * 2], inv[2][maxn * 2];
int N, na, nb, rev[maxn * 2], ans[maxn * 2];
string s1, s2;

void FFT(Complex *a, int f)//f表示DFT(0)还是IDFT(1)
{
    Complex x, y;
    for(int i = 0; i < N; i++)
        if(i < rev[i]) //不加这条if会交换两次(就是没交换)
            swap(a[i], a[rev[i]]);
    for(int i = 1; i < N; i <<= 1) //i是准备合并序列的长度的二分之一
        for(int j = 0, t = N / (i << 1); j < N; j += i << 1) //i*2是准备合并序列的长度,j是合并到了哪一位(第某段的开头的坐标),t表示每一份单位根占单位圆的多少
            for(int k = 0, l = 0; k < i; k++, l += t) //k是第某段内的第i位(只扫描前一半,后面一半可以同时求)
            {
                x = inv[f][l] * a[j + k + i]; //inv[f][l]表示第L份单位根
                y = a[j + k];
                a[j + k] = y + x;
                a[j + k + i] = y - x;
            }
    if(f)
        for(int i = 0; i < N; i++)
            a[i].re /= N;
}

void Init()
{
    int bit = 0;
    while((1 << bit) < N)
        bit++;
    for(int i = 0; i < N; i++)//预处理逆反位置
    {
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    }

    for(int i = 0; i < N; i++)//预处理单位根
        inv[0][i] = inv[1][i] = Complex(cos(2 * pi * i / N), sin(2 * pi * i / N)), inv[1][i].im = -inv[0][i].im;
}

void pre()
{
    na = s1.length();
    nb = s2.length();

    for(N = 1; N < na || N < nb; N <<= 1);
    N <<= 1;

    for(int i = 0; i < N; i++)//多组输入记得清空数组
    {
        if(i < na)
            a[i].re = s1[na - 1 - i] - '0', a[i].im = 0;
        else
            a[i].re = a[i].im = 0;
        if(i < nb)
            b[i].re = s2[nb - 1 - i] - '0', b[i].im = 0;
        else
            b[i].re = b[i].im = 0;
        ans[i] = 0;
    }
}

void work()
{
    Init();
    FFT(a, 0), FFT(b, 0);
    for(int i = 0; i < N; i++)
        a[i] = a[i] * b[i];
    FFT(a, 1);

    for(int i = 0; i < N; i++)//进位处理
    {
        ans[i] += (int)(a[i].re + 0.5);
        ans[i + 1] += ans[i] / 10;
        ans[i] %= 10;
    }

    int flag = 0;
    for(int i = N - 1; i >= 0; i--)//输出处理
    {
        if(ans[i])
        {
            printf("%d", ans[i]);
            flag = 1;
        }
        else if(flag || i == 0)//如果不是前导0 或者 (全部都是0,那么flag = 0,到最后一位时,0不能忽略,必须输出一个0)
            printf("0");
    }
    printf("\n");
}

int main()
{
    while(cin >> s1 >> s2)
    {
        pre();
        work();
    }
    return 0;
}



七、应用-- 字符串模糊匹配

  1. 题意:模板串P和文本串T都带有?号,可以匹配任意一个字符,求P在T中所有的出现位置。

  2. 定义:文本偏移量 f ( A , B ) = s i g m a ( ∣ A [ i ] − B [ i ] ∣ ) f(A,B) = sigma(|A[i] - B[i]|) f(A,B)=sigma(A[i]B[i])
    由于绝对值不好求,故转化为 平方
    f ( A , B ) = s i g m a ( ( ∣ A [ i ] − B [ i ] ∣ ) 2 ) = s i g m a A [ i ] 2 + s i g m a B [ i ] 2 − 2 ∗ s i g m a A [ i ] ∗ B [ i ] f(A,B) = sigma((|A[i] - B[i]|)^2)= sigmaA[i] ^2+sigmaB[i] ^2-2*sigmaA[i] *B[i] f(A,B)=sigma(A[i]B[i])2=sigmaA[i]2+sigmaB[i]22sigmaA[i]B[i]

  3. 由于在多项式乘法中, C k ( 满 足 i + j = k ) C_k(满足i+j=k) Ck(i+j=k)

    • 故对于字符串 A [ 0... ( N − 1 ) ] A[0...(N-1)] A[0...(N1)] B [ 0... ( N − 1 ) ] B[0...(N-1)] B[0...(N1)],我们需要将其中一个逆反,才能满足上面的算式。
    • b [ 0... ( N − 1 ) ] b[0...(N-1)] b[0...(N1)] 其中 b [ i ] = [ N − i − 1 ] b[i] = [N-i-1] b[i]=[Ni1](b是B的逆反串)
    • A [ i ] A[i] A[i] B [ i ] B[i] B[i]比较,相当于 A [ i ] A[i] A[i] b [ N − i − 1 ] b[N-i-1] b[Ni1]比较,而 i + ( N − i − 1 ) ≡ N − 1 i+(N-i-1)\equiv N-1 i+(Ni1)N1
    • 因此,我们可以构造两个多项式,A,b的第i位上的字符为 x i x^i xi的系数,求 f ( A , b ) f(A, b) f(A,b),看第N位上的系数是否为0,即可判断是否匹配。
    • 以上是没有通配符?的情况,显然这个复杂度O(nlogn)比kmp的O(n)要大,不划算。
  4. 若有通配符?,我们只需要改变一下函数 f ( A , B ) f(A,B) f(A,B)即可。

    • 令? = 0,其他符号映射非零数值
    • 那么只要有?不管另一个是什么,都能够匹配,即 f ( ) f() f()均等于0.
    • f ( A , B ) = s i g m a ( ( A [ i ] − B [ i ] ) 2 ∗ A [ i ] ∗ B [ i ] ) f(A,B)=sigma((A[i]-B[i])^2*A[i]*B[i]) f(A,B)=sigma((A[i]B[i])2A[i]B[i])
  5. 若要计算 A字符串(N)、B字符串(M),在B中有多少个 B [ i , i + N ) B [i,i+N) B[i,i+N)区间,能够满足有 [ N ∗ 0.75 ] [N*0.75] [N0.75]的字符与A匹配(不要求连续),即有75%的字符与B子区间对应相同。

  • 假设字符集 ∣ S ∣ |S| S中为a~b
  • A [ i ] = = a A[i]==a A[i]==a,则令 a [ i ] = 1 a[i]=1 a[i]=1,否则 a [ i ] = 0 a[i]=0 a[i]=0 i ∈ 1... n i \in {1...n} i1...n
  • B [ i ] = = a B[i]==a B[i]==a,则令 b [ i ] = 1 b[i]=1 b[i]=1,否则 b [ i ] = 0 b[i]=0 b[i]=0 i ∈ 1... n i \in {1...n} i1...n
  • 然后构造 f ( A , B ) = s i g m a ( a [ i ] ∗ b [ i ] ) f(A,B)=sigma(a[i]*b[i]) f(A,B)=sigma(a[i]b[i])
  • 直接可求 a [ i ] = a [ i ] ∗ b [ i ] a[i]=a[i]*b[i] a[i]=a[i]b[i]
  • 然后统计 c n t [ ‘ a ’ ] + = a [ i ] cnt [‘a’]+= a[i] cnt[a]+=a[i],cnt[a]即表示字符a一致的位置有多少个。
  • 我们可以按照以上方法,对字符集中的每一个字符进行一遍计算,再对cnt[ ]求和,就可以得到最后匹配位置的总和。

你可能感兴趣的:(数论,-,FFT)