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
Note: the length of each integer will not exceed 50000.
# 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
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.
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.
题意:给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
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 0≤s≤50000 and 1≤n≤100000.
题意:给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 ![](http://file.51nod.com/images/icon/ok.png)
给出一个长度为n的数组a。区间[L,R]的值为 ∑R−Li=0a[L+i]∗a[R−i]
求所有长度为质数的区间的值的总和。
第一行一个数n(1<=n<=100000) 第二行n个数,表示数组a(0<=a[i]<=1000)
一个数表示答案,答案对10^9+7取模。
4 1 2 3 4
75
# 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;
}