[FFT] 例题详解

FFT是什么

FFT(Fast Fourier Transformation)就是“快速傅里叶变换”的意思,它是一种用来计算DFT(离散傅里叶变换)和IDFT(离散傅里叶反变换)的一种快速算法。这种算法运用了一种高深的数学方式、把原来复杂度为O(n2)的朴素多项式乘法转化为了O(nlogn)的算法。


FFT在算法竞赛中的例题

FFT能将多项式乘法O(n^2) 优化为 O(nlogn)

但是题目中并不是直接了当的求多项式乘法,而是将问题转化成多项式乘法的形式。

高精度乘法-HDU1402

众所周知高精度乘法其实是两个字符串模拟乘法,而两个字符串可以看成两个多项式,满足多项式乘法的性质。

可以使用FFT加速

#include
using namespace std;
const double pi = acos(-1.0);
// 复数结构体
struct Complex {
    double x,y;
    Complex(double _x = 0.0,double _y = 0.0) {
        x = _x;
        y = _y;
    }
    Complex operator - (const Complex &b)const {
        return Complex(x-b.x,y-b.y);
    }
    Complex operator + (const Complex &b)const {
        return Complex(x+b.x,y+b.y);
    }
    Complex operator * (const Complex &b)const {
        return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
    }
};
/* 进行FFT和IFFT前的反转变化
 * 位置 i 和 (i 二进制反转后位置) 互换
 * len 必须为 2 的整数幂
 */
void change(Complex y[],int len) {
    int i,j,k;
    for(i=1,j=len/2;i= k) {
            j -= k;
            k /= 2;
        }
        if(j < k) j += k;
    }
}
/*
 * FFT
 * len必须是2^k形式
 * on = 1是FFT, -1是IFFT
 */
void fft(Complex y[],int len,int on) {
    change(y,len);
    for(int h=2;h<=len;h<<=1) {
        Complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
        for(int j=0;j0) len--;
        for(int i=len;i>=0;i--) 
            printf("%c",sum[i]+'0');
        printf("\n");
    }
    return 0;
}

数组卷积 HDU4609

题意

题目是给了n条线段。问随机取三个,可以组成三角形的概率。

首先题目给了a数组,

如样例一:

4

1 3 3 4

把这个数组转化成num数组,num[i]表示长度为i的有num[i]条。

样例一就是

num = {0   1   0    2    1}

代表长度0的有0根,长度为1的有1根,长度为2的有0根,长度为3的有两根,长度为4的有1根。

使用FFT解决的问题就是num数组和num数组卷积。

num数组和num数组卷积的解决,其实就是从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数,他们的和每个值各有多少个

例如{0 1 0 2 1}*{0 1 0 2 1} 卷积的结果应该是{0 0  1  0  4  2  4  4  1 }

长度为n的数组和长度为m的数组卷积,结果是长度为n+m-1的数组。

 

{0 1 0 2 1}*{0 1 0 2 1} 卷积的结果应该是{0 0  1  0  4  2  4  4  1 }。

这个结果的意义如下:

从{1 3 3 4}取一个数,从{1 3 3 4}再取一个数

取两个数和为 2 的取法是一种:1+1

           和为 4 的取法有四种:1+3, 1+3  ,3+1 ,3+1

           和为 5 的取法有两种:1+4 ,4+1;

           和为 6的取法有四种:3+3,3+3,3+3,3+3,3+3

           和为 7 的取法有四种: 3+4,3+4,4+3,4+3

           和为 8 的取法有 一种:4+4

 

利用FFT可以快速求取循环卷积,具体求解过程不解释了,就是DFT和FFT的基本理论了。

总之FFT就是快速求到了num和num卷积的结果。只要长度满足>=n+m+1.那么就可以用循环卷积得到线性卷积了。

 

弄完FFT得到一个num数组,这个数组的含义在上面解释过了。

while(len < 2*len1) len <<= 1;
for(int i=0;i

这里代码中的num数组就是卷积后的结果,表示两两组合。

但是题目中本身和本身组合是不行的,所有把取同一个的组合的情况删掉。

len = 2*a[n-1];
for(int i=0;i

还有,这个问题求组合,所以第一个选t1,第二个选t2,和第一个选t2,第二个选t1,我们认为是一样的。

所有num数组整体除于2

for(int i=1;i<=len;i++) num[i]/=2;

然后对num数组求前缀和

sum[0] = 0;
for(int i=1;i<=len;i++) sum[i] = sum[i-1]+num[i];

之后就开始O(n)找可以形成三角形的组合了。

a数组从小到大排好序。

 

对于a[i].  我们假设a[i]是形成的三角形中最长的。这样就是在其余中选择两个和>a[i],而且长度不能大于a[i]的。(注意这里所谓的大于小于,不是说长度的大于小于,其实是排好序以后的,位置关系,这样就可以不用管长度相等的情况,排在a[i]前的就是小于的,后面的就是大于的)。

根据前面求得的结果。

长度和大于a[i]的取两个的取法是sum[len]-sum[a[i]].

但是这里面有不符合的。

一个是包含了取一大一小的

cnt -= (long long)(n-1-i)*i;

一个是包含了取一个本身i,然后取其它的

cnt -= (n-1);

还有就是取两个都大于的了

cnt -= (long long)(n-1-i)*(n-i-2)/2;

 

这样把i从0~n-1累加,就答案了。

ll cnt = 0;
for(int i=0;i

代码

    #include
    using namespace std;
    const double pi = acos(-1.0);
    struct Complex {
    double x,y;
    Complex(double _x = 0.0,double _y = 0.0) {
        x = _x;
        y = _y;
    }
    Complex operator - (const Complex &b)const {
        return Complex(x-b.x,y-b.y);
    }
    Complex operator + (const Complex &b)const {
        return Complex(x+b.x,y+b.y);
    }
    Complex operator * (const Complex &b)const {
        return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
    }
    };
    void change(Complex y[],int len) {
    int i,j,k;
    for(i=1,j=len/2;i= k) {
            j -= k;
            k /= 2;
        }
        if(j < k) j += k;
    }
    }
    void fft(Complex y[],int len,int on) {
    change(y,len);
    for(int h=2;h<=len;h<<=1) {
        Complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
        for(int j=0;j

原根+FFT

题目

[FFT] 例题详解_第1张图片

[FFT] 例题详解_第2张图片


题解

代码

    /**
     * 给出n个数a1,a2,...,an,问对于每一个ak,求有多少个有序二元组(i,j)满足(ai*aj) mod P = ak
     * 求出 P 的原根 g
     * 上式变成 g^bi*g^bj mod P = g^bk
     * bi + bj == bk 
     * 举个例子 p = 7,7 的原根 g = 3
     * b           0   1   2   3   4   5    6   7   8   9   10   11    12         
     * g^bi        1   3   9   27  81  243  
     * g^bi mod p  1   3   2   6   4   5    1   3   2   6   4    5     1
     * 我们可以观察到这里 p-1 为循环节,那么我们只需要记录前p-1个bi即可。
     * 我们设I[x] = pos,表示当取模的值为x时 ,bi 为多少,如I[3] = 1,I[2] = 2,I[6] = 3;
     * 那么对于输入的n个数,我们就可以知道每个数x的I[x]为多少。
     * 然后再记录一下每个bi出现了多少次,用num[]记录。
     * 对num[]数组做fft自我卷积得到了sum[]数组。
     * sum[]数组表示sum[k] = num[i]*num[j] (k == i+j) 即和为k时的二元组的个数
     * 当时需要注意的是卷积之后的k的范围为[0,2*p-3)
     * 之后的[p-1,2*p-3)需要归入[0,p-1)中,因为其是循环节则我们用i%(p-1)归入即可。
     * 
    */
    #include
    using namespace std;
    const double pi = acos(-1.0);
    // 复数结构体
    struct Complex {
        double x,y;
        Complex(double _x = 0.0,double _y = 0.0) {
            x = _x;
            y = _y;
        }
        Complex operator - (const Complex &b)const {
            return Complex(x-b.x,y-b.y);
        }
        Complex operator + (const Complex &b)const {
            return Complex(x+b.x,y+b.y);
        }
        Complex operator * (const Complex &b)const {
            return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
        }
    };
    /* 进行FFT和IFFT前的反转变化
    * 位置 i 和 (i 二进制反转后位置) 互换
    * len 必须为 2 的整数幂
    */
    void change(Complex y[],int len) {
        int i,j,k;
        for(i=1,j=len/2;i= k) {
                j -= k;
                k /= 2;
            }
            if(j < k) j += k;
        }
    }
    /*
    * FFT
    * len必须是2^k形式
    * on = 1是FFT, -1是IFFT
    */
    void fft(Complex y[],int len,int on) {
        change(y,len);
        for(int h=2;h<=len;h<<=1) {
            Complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
            for(int j=0;j>= 1;
        }
        return res;
    }
    ll getEular(ll p) {
        ll ans = p;
        for(ll i = 2;i*i <= p;i++) {
            if(p % i == 0) {
                ans -= ans / i;
                while(p % i == 0) p/=i;
            }
        }
        if(p > 1) ans -= ans / p;
        return ans;
    }
    ll getRoot(ll p) {
        ll fip = p-1;
        if(p == 2) return 1;
        getFactor(fip);
        for(ll g=2;g= P) printf("0\n");
                else {
                    if(brr[i]) printf("%lld\n",c[I[brr[i]]]);
                    else printf("%lld\n",ansz);
                }
            }
        }
        return 0;
    }

 

你可能感兴趣的:(FFT)