数论:快速傅里叶变换FFT题集

FFT(Fast Fourier Transformation——快速傅里叶变换)可以用来快速求多项式乘法,相当于系数向量的卷积,通俗点说就是给定a和b数组,然后求c数组:

for(int i=0; i<n; ++i)
     for(int j=0; j<n; ++j)
         c[i+j] += a[i]*b[j];

FFT就是用来快速求出上述的c数组,FFT分成如下两个过程:

DFT——Discrete Fourier Transform 离散傅里叶逆变换
IDFT——Inverse Discrete Fourier Transform 离散傅里叶逆变换

通过计算机算法可以O(nlogn)执行以上两个过程,具体做法和证明请参考其他资料。那么FFT有什么用呢?比如大数乘法,即相当于变项X=10的多项式乘法,那么我们对它的系数求卷积就完成了。

1.HDU 1402

A * B Problem Plus

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 24638    Accepted Submission(s): 6276


Problem Description
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
 
 
1210002
 

Sample Output
 
 
22000
 

Author
DOOM III
题意:大数乘法。
思路:模板题。 
# include <iostream>
# include <cstdio>
# include <cmath>
# include <cstring>
# include <complex>
using namespace std;
typedef complex <double> cp;
const int maxn = 550010;
const double pi = acos(-1.0);
char sa[maxn], sb[maxn];
int lena, lenb, N, n=1, l=0, res[maxn], R[maxn];
cp a[maxn], b[maxn];
void fft(cp *A, int f)
{
    for(int i=0; i<n; ++i)
        if(i<R[i]) swap(A[i], A[R[i]]);
    for(int i=1; i<n; i<<=1)
    {
        cp wn(cos(pi/i), sin(f*pi/i)), x, y;
        for(int j=0; j<n; j+=(i<<1))
        {
            cp w(1, 0);
            for(int k=0; k<i; ++k,w*=wn)
            {
                x = A[j+k], y = w*A[i+j+k];
                A[j+k] = x+y;
                A[i+j+k] = x-y;
            }
        }
    }
}
int main()
{
    while(~scanf("%s%s",sa,sb))
    {
        n = 1; N = l = 0;
        memset(res, 0, sizeof(res));
        lena = strlen(sa), lenb = strlen(sb);
        while(n<=lena+lenb) n<<=1,++l;//补成2的幂次大小
        for(int i=0; i<lena; ++i) a[i] = sa[lena-i-1]-'0';
        for(int i=lena; i<=n; ++i) a[i] = 0;
        for(int i=0; i<lenb; ++i) b[i] = sb[lenb-i-1]-'0';
        for(int i=lenb; i<=n; ++i) b[i] = 0;
        for(int i=0; i<=n; ++i) R[i] = (R[i>>1]>>1)|((i&1)<<(l-1));
        fft(a, 1);
        fft(b, 1);
        for(int i=0; i<=n; ++i) a[i] *= b[i];
        fft(a, -1);
        for(int i=0; i<=lena+lenb; ++i)
        {
            res[i] += int(a[i].real()/n+0.5);
            res[i+1] += res[i]/10;
            res[i] %= 10;
            if(res[i]>0) N=i;
        }
        for(int i=N; ~i; --i)
            putchar(res[i]+'0');
        puts("");
    }
    return 0;
}

2.HDU4609:

3-idiots

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 6656    Accepted Submission(s): 2335


Problem Description
King OMeGa catched three men who had been streaking in the street. Looking as idiots though, the three men insisted that it was a kind of performance art, and begged the king to free them. Out of hatred to the real idiots, the king wanted to check if they were lying. The three men were sent to the king's forest, and each of them was asked to pick a branch one after another. If the three branches they bring back can form a triangle, their math ability would save them. Otherwise, they would be sent into jail.
However, the three men were exactly idiots, and what they would do is only to pick the branches randomly. Certainly, they couldn't pick the same branch - but the one with the same length as another is available. Given the lengths of all branches in the forest, determine the probability that they would be saved.
 

Input
An integer T(T≤100) will exist in the first line of input, indicating the number of test cases.
Each test case begins with the number of branches N(3≤N≤10 5).
The following line contains N integers a_i (1≤a_i≤10 5), which denotes the length of each branch, respectively.
 

Output
Output the probability that their branches can form a triangle, in accuracy of 7 decimal places.
 

Sample Input
 
 
241 3 3 442 3 3 4
 

Sample Output
 
 
0.50000001.0000000
 

题意:给N条棍子,任选3条能够组成三角形的概率。

思路:用FFT求出用2条棍子组成长度为i的棍子的方案数,然后枚举选取的最长棍子,减去非法的方案即可。

# include <iostream>
# include <cstdio>
# include <cmath>
# include <cstring>
# include <algorithm>
# include <complex>
using namespace std;
typedef long long LL;
typedef complex <double> cp;
const int maxn = 550010;
const double pi = acos(-1.0);
int lena, lenb, n=1, l=0, N, R[maxn], arr[maxn];
cp a[maxn];
LL res[maxn], ans;
void fft(cp *A, int f)
{
    for(int i=0; i<n; ++i)
        if(i<R[i]) swap(A[i], A[R[i]]);
    for(int i=1; i<n; i<<=1)
    {
        cp wn(cos(pi/i), sin(f*pi/i)), x, y;
        for(int j=0; j<n; j+=(i<<1))
        {
            cp w(1, 0);
            for(int k=0; k<i; ++k,w*=wn)
            {
                x = A[j+k], y = w*A[i+j+k];
                A[j+k] = x+y;
                A[i+j+k] = x-y;
            }
        }
    }
}
int main()
{
    int T, x;
    scanf("%d",&T);
    while(T--)
    {
        ans = 0;
        n = 1; l = 0;
        scanf("%d",&N);
        memset(res, 0, sizeof(res));
        int imax = 0;
        for(int i=1; i<=N; ++i)
        {
            scanf("%d",&arr[i]);
            imax = max(imax, arr[i]);
            ++res[arr[i]];
        }
        while(n<=imax*2) n<<=1,++l;
        for(int i=0; i<n; ++i) a[i] = res[i];
        for(int i=0; i<=n; ++i) R[i] = (R[i>>1]>>1)|((i&1)<<(l-1));
        fft(a, 1);
        for(int i=0; i<n; ++i) a[i] *= a[i];
        fft(a, -1);
        for(int i=0; i<n; ++i) res[i] = LL(a[i].real()/n+0.5);
        for(int i=1; i<=N; ++i) --res[arr[i]*2];
        for(int i=0; i<n; ++i) res[i]>>=1;
        for(int i=1; i<=n; ++i) res[i] += res[i-1];
        sort(arr+1, arr+1+N);
        for(int i=1; i<=N; ++i)
        {
            ans += res[n]-res[arr[i]];
            ans -= 1LL*(N-i)*(N-i-1)/2;//都大于arr[i]
            ans -= 1LL*(N-i)*(i-1);//一条大于arr[i]
            ans -= N-1;//一条等于arr[i]
        }
        printf("%.7f\n",ans*1.0/(1LL*N*(N-1)*(N-2)/6));
    }
    return 0;
}

3.HDU 5307

He is Flying

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 1479    Accepted Submission(s): 419


Problem Description
JRY wants to drag racing along a long road. There are  n sections on the road, the  i-th section has a non-negative integer length  si. JRY will choose some continuous sections to race (at an unbelievable speed), so there are totally  n(n+1)2 different ways for him to ride. If JRY rides across from the  i-th section to the  j-th section, he would gain  ji+1 pleasure. Now JRY wants to know, if he tries all the ways whose length is  s, what's the total pleasure he can get. Please be aware that in the problem, the length of one section could be zero, which means that the length is so trivial that we can regard it as  0.
 

Input
The first line of the input is a single integer  T (T=5), indicating the number of testcases. 

For each testcase, the first line contains one integer  n. The second line contains  n non-negative integers, which mean the length of every section. If we denote the total length of all the sections as  s, we can guarantee that  0s50000 and  1n100000.
 

Output
For each testcase, print  s+1 lines. The single number in the  i-th line indicates the total pleasure JRY can get if he races all the ways of length  i1.
 

Sample Input
 
 
231 2 340 1 2 3
 

Sample Output
 
 
01130231316027
 

Author
XJZX
 

题意:给N个数,问所有和为X的连续区间的长度总和是多少,X从0到sum都要输出答案。

思路:连续区间容易想到前缀和,所有区间的贡献,容易想到前缀和取反后跑FFT,前缀和作为多项式指数(数组下标),但是由于FTT是乘法,无法将贡献跟多项式的系数(数组的值)联系起来,那么我们先求出sum[i]代表和为i的连续区间的右界总和,再求出sum2[i]代表和为i的连续区间的左界总和,sum[i]-sum2[i]就是和为i的答案,这两者就容易求了。因为用这里用FFT加速前缀和相减,所以0要特殊处理!另外这题不能用STL进行复数运算否则卡精度。

#include <bits/stdc++.h>
using namespace std;
#define N 110000
#define ll long long
#define ld long double
const ld PI=acos(-1);
int T,n;
int sum[N],cnt;
ll ans[N],pre[N];
struct cp
{
    ld x,y;
    cp(){}
    cp(ld x,ld y):x(x),y(y){}
    friend cp operator + (const cp &r1,const cp &r2)
    {return cp(r1.x+r2.x,r1.y+r2.y);}
    friend cp operator - (const cp &r1,const cp &r2)
    {return cp(r1.x-r2.x,r1.y-r2.y);}
    friend cp operator * (const cp &r1,const cp &r2)
    {return cp(r1.x*r2.x-r1.y*r2.y,r1.x*r2.y+r2.x*r1.y);}
}a[N<<1],b[N<<1],c[N<<1];
void init()
{
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    memset(c,0,sizeof(c));
}
void FFT(cp *a,int len,int type)
{
    int t=0;
    for(int i=0;i<len;i++)
    {
        if(i>t)swap(a[i],a[t]);
        for(int j=len>>1;(t^=j)<j;j>>=1);
    }
    for(int i=2;i<=len;i<<=1)
    {
        cp wn(cos(2*PI*type/i),sin(2*PI*type/i));
        for(int j=0;j<len;j+=i)
        {
            cp t,w(1,0);
            for(int k=0;k<(i>>1);k++,w=w*wn)
            {
                t=w*a[j+k+(i>>1)];
                a[j+k+(i>>1)]=a[j+k]-t;
                a[j+k]=a[j+k]+t;
            }
        }
    }
}
void solve(cp *a,cp *b,cp *c,int len)
{
    FFT(a,len,1);FFT(b,len,1);
    for(int i=0;i<len;i++)c[i]=a[i]*b[i];
    FFT(c,len,-1);
}
int main()
{
    for(int i=1;i<=100000;i++)
        pre[i]=pre[i-1]+(ll)i*(i+1)/2;
    scanf("%d",&T);
    while(T--)
    {
        memset(ans,0,sizeof(ans));
        scanf("%d",&n);cnt=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sum[i]);
            if(sum[i]==0)cnt++;
            else ans[0]+=pre[cnt],cnt=0;
            sum[i]+=sum[i-1];
        }
        ans[0]+=pre[cnt];
        int len=1;for(;len<=sum[n]<<1;len<<=1);
        init();
        for(int i=1;i<=n;i++)
            a[sum[i]].x+=i,b[sum[n]-sum[i-1]].x++;
        solve(a,b,c,len);
        for(int i=1;i<=sum[n];i++)
            ans[i]+=(ll)(c[i+sum[n]].x/len+0.1);

        init();
        for(int i=1;i<=n;i++)
            a[sum[i]].x++,b[sum[n]-sum[i-1]].x+=i-1;
        solve(a,b,c,len);
        for(int i=1;i<=sum[n];i++)
            ans[i]-=(ll)(c[i+sum[n]].x/len+0.1);

        for(int i=0;i<=sum[n];i++)
            printf("%lld\n",ans[i]);
    }
    return 0;
}

4.51nod 1690 区间求和2 

给出一个长度为n的数组a。区间[L,R]的值为 RLi=0a[L+i]a[Ri]

求所有长度为质数的区间的值的总和。 

Input
第一行一个数n(1<=n<=100000)
第二行n个数,表示数组a(0<=a[i]<=1000)
Output
一个数表示答案,答案对10^9+7取模。
Input示例
4
1 2 3 4
Output示例
75
思路:考虑每一个点对(i,j)的贡献,显然i和j的奇偶性相同才有贡献(除了长度为2的区间特殊处理),那么(i,j)的贡献显然是
a[i]*a[j]*F[min(i+j−1,2n+1−i−j)]] - a[i]*a[j]*F[j-i)]。其中F[i]表示1~i有几个质数(不包括2),即右边和左边能延伸的长度的较少值,公式左边FFT直接搞算出res[i]表示下标和为i的二元组总和,右边也是FFT,res[i]理论应该算出区间长度为i的二元组总和,但是这样算不了,需要将原数组反转一下,具体看代码纸上模拟下就ok。

# include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 5e5+30;
typedef complex <double> cp;
const double pi = acos(-1.0);
const LL mod = 1e9+7;
int n=1, l=0,N, R[maxn], c[maxn], p[maxn];
LL ans=0, res[maxn];
cp a[maxn], b[maxn];
void fft(cp *A, int f)
{
    for(int i=0; i<n; ++i)
        if(i<R[i]) swap(A[i], A[R[i]]);
    for(int i=1; i<n; i<<=1)
    {
        cp wn(cos(pi/i), sin(f*pi/i)), x, y;
        for(int j=0; j<n; j+=(i<<1))
        {
            cp w(1, 0);
            for(int k=0; k<i; ++k,w*=wn)
            {
                x = A[j+k], y = w*A[i+j+k];
                A[j+k] = x+y;
                A[i+j+k] = x-y;
            }
        }
    }
}
void init()
{
    for(int i=3; i<=300000; ++i)
    {
        int flag = 1;
        for(int j=2; j<=sqrt(i); ++j)
        {
            if(i%j==0)
            {
                flag = 0;
                break;
            }
        }
        p[i] = p[i-1]+flag;
    }
}
inline void add(LL &x, LL y)
{
    x += y%mod;
    x = (x%mod+mod)%mod;
}
int main()
{
    init();
    scanf("%d",&N);
    for(int i=1; i<=N; ++i)
    {
        scanf("%d",&c[i]);
        if(i>1) add(ans, c[i]*c[i-1]%mod*2);
    }
    while(n<=N+N) n<<=1, ++l;
    for(int i=0; i<=N; ++i) a[i] = c[i];
    for(int i=0; i<=n; ++i) R[i] = (R[i>>1]>>1)|((i&1)<<(l-1));
    fft(a, 1);
    for(int i=0; i<=n; ++i) a[i] *= a[i];
    fft(a, -1);
    for(int i=1; i<=N+N; ++i) res[i] = LL(a[i].real()/n+0.5)%mod;
    for(int i=4; i<=N+1; i+=2) add(ans, res[i]*p[i-1]);
    for(int i=N+2; i<=N+N; ++i) if(!(i&1))add(ans, res[i]*p[2*N+1-i]);
    memset(a, 0, sizeof(a));
    for(int i=1; i<=N; ++i)
    {
        a[i] = c[i];
        b[i] = c[N-i+1];
    }
    fft(a, 1);
    fft(b, 1);
    for(int i=0; i<=n; ++i) a[i] *= b[i];
    fft(a, -1);
    for(int i=1; i<=N+N; ++i) res[i] = LL(a[i].real()/n+0.5);
    for(int i=3; i<=N; i+=2)
        add(ans, p[i-1]*res[N-i+2]%mod*2*-1);
    printf("%lld\n",ans);
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值